diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..98f72a30a9d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +ident_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..dad040e494f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ + +``` + * JGraphT version: + * Java version (java -version)/platform: +``` + +**Issue** + + + +**Steps to reproduce (small coding example)** + + + +**Expected behaviour** + + + +**Other information** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..17ea0d63eea --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ + + +---- + +- [ ] 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) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..c9b2105b448 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/PR-workflow.yaml b/.github/workflows/PR-workflow.yaml new file mode 100644 index 00000000000..d9a65b88aa2 --- /dev/null +++ b/.github/workflows/PR-workflow.yaml @@ -0,0 +1,34 @@ +name: JGrapht Pull Request build +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - name: Windows + tag: windows-latest + - name: macOS + tag: macos-latest + - name: Ubuntu + tag: ubuntu-latest + name: Build (${{ matrix.os.name }}) + runs-on: ${{ matrix.os.tag }} + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + cache: 'maven' + - name: Build with Maven + shell: bash + run: | + set -e + mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn verify -B && mvn javadoc:aggregate && mvn checkstyle:check -P checkstyle diff --git a/.github/workflows/master-workflow.yaml b/.github/workflows/master-workflow.yaml new file mode 100644 index 00000000000..67fcb3fc322 --- /dev/null +++ b/.github/workflows/master-workflow.yaml @@ -0,0 +1,83 @@ +name: JGrapht Master build +on: + push: + branches: + - master + paths-ignore: + - 'CONTRIBUTORS.md' + - 'HISTORY.md' + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + os: + - name: Windows + tag: windows-latest + - name: macOS + tag: macos-latest + - name: Ubuntu + tag: ubuntu-latest + name: Build (${{ matrix.os.name }}) + runs-on: ${{ matrix.os.tag }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + cache: 'maven' + + - name: Build with Maven + shell: bash + run: | + set -e + mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn verify -B && mvn javadoc:aggregate && mvn checkstyle:check -P checkstyle + + snapshot-publish: + name: Publish Snapshot + needs: build + if: github.repository == 'jgrapht/jgrapht' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + cache: 'maven' + - run: | + set -e + mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn javadoc:aggregate + - uses: actions/setup-node@v4 + with: + node-version: 14 + - run: npm install -g hercule@5.0.0 + + - name: Run prepareDocs script + run: ./etc/prepareDocs.sh + shell: bash + + - name: Deploy snapshot to Sonatype + env: + CENTRAL_USER: ${{ secrets.CI_DEPLOY_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + run: mvn package org.sonatype.central:central-publishing-maven-plugin:publish -DskipTests=true --settings etc/snapshot-settings.xml + shell: bash + + - name: Publish Github Pages + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + token: ${{ secrets.PAGES_TOKEN }} + branch: gh-pages + folder: docs + clean: true diff --git a/.gitignore b/.gitignore index 9c0a3e69cc9..f9321ce628f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,14 @@ build .kdev4 .buildpath .svn +.idea/* +*.iml +out/* +**/out/* +**/bin/* +pom.xml.releaseBackup +**/pom.xml.releaseBackup +**/dependency-reduced-pom.xml +.classpath +docs/javadoc* +docs/_site diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..d3e0412f3e6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,90 @@ +# 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..37ec37d6277 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +Thanks 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. + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ef10618e988..0ae33757d40 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,9 +1,15 @@ ## 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](http://sourceforge.net/users/barak_naveh/) (project founder) -- [John V Sichi](http://sourceforge.net/users/perfecthash/) (current project administrator) +- [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/) @@ -21,7 +27,6 @@ JGraphT wouldn't be the library it is today without the source contributions and - Carl Anderson - Khanh Vu - Aaron Harnly -- Dimitrios Michail - Welson Sun - Trevor Harmon - David Black-Schaffer @@ -41,15 +46,124 @@ JGraphT wouldn't be the library it is today without the source contributions and - Tom Conerly - Michele Mancioppi - Adrian Marte -- Assaf Mizrachi +- [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) +- [Rostislav Svoboda](https://github.com/rsvoboda) +- [Dariusz Dudek](https://github.com/dpdudek) +- [Kaiichiro Ota](https://github.com/kigh-ota) +- [Magnus Gunnarsson](https://github.com/EnderCrypt) +- [Frans van Buul](https://github.com/fransvanbuul) +- [Sérgio Faria](https://github.com/sergio91pt) +- [Rene Leonhardt](https://github.com/reneleonhardt) +- [DelfinSR](https://github.com/DelfinSR) +- [vab2048](https://github.com/vab2048) +- [Sung Ho Yoon](https://github.com/syoon2) +- [Albgarsan](https://github.com/Albgarsan) +- [J. Alejandro Cornejo-Acosta](https://github.com/alex-cornejo) +- [Feng Wenhan](https://github.com/fwhdzh) +- [Yuri Bilyarov](https://github.com/YuriBilyarov) +- [Antonia Tsiftsi](https://github.com/toniaTsif) +- Lena Büttel +- [Kirill A. Korinsky](https://github.com/catap) -(if we have missed your name on this list, please email us to get it fixed). +(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. +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. \ No newline at end of file +Many thanks to all of you. diff --git a/HISTORY.md b/HISTORY.md index c1a6babff50..c985dea25ca 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,137 +2,637 @@ Changes to JGraphT in each version: -- **version 0.8.4** (under development): - - Move to github for source control, and Apache Maven for build, contributed by Andreas Schnaiter and Owen Jacobson. - - 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. +- **version 1.5.3** (Under development) + - Updated dependencies (contributed by Joris Kinable and Dimitrios Michail) + - Fixed a bug in `DOTExporter` causing graph attributes not to be exported correctly. (contributed by vab2048) + - Fixed `DoublyLinkedList` for compatibility with Java 21 (reported by Liam Miller-Cushon, contributed by Sung Ho Yoon) + - Fixed a bug in `GmlExporter` to use system line separator consistently (contributed by Sung Ho Yoon) + - Migrated to JUnit 5 (contributed by Sung Ho Yoon) + - Javadoc, Maven, README, and CI maintenance (contributed by Sung Ho Yoon) + - Miscellaneous code maintenance (contributed by Sung Ho Yoon) + - Added missing license headers (contributed by Sung Ho Yoon) + - Added matrix build (Ubuntu, MacOS, Windows) to CI (contributed by Sung Ho Yoon) + - Tidied up logging in `VF2SubgraphIsomorphismState` (contributed by Albgarsan) + - Added `FarthestInsertionHeuristicTSP` algorithm (contributed by J. Alejandro Cornejo-Acosta) + - Prevent edge weight modification in `AsUnmodifiableGraph` and add tests (contributed by Sung Ho Yoon) + - Rename `SorensenIndexLinkPrediction` to remove non-ASCII characters (contributed by Feng Wenhan) + - Fixed backslashing interpretation in `DOTEventDrivenImporter` (contributed by Feng Wenhan) + - Fixed `SuurballeKDisjointShortestPaths` modified weight calculation (contributed by Yuri Bilyarov) + - Added `KouMarkowskyBermanAlgorithm` implementation of `SteinerTreeAlgorithm` (contributed by Lena Büttel and Dimitrios Michail) + - Added `GreedyModularityAlgorithm` and `NaiveGreedyModularityAlgorithm` (contributed by Antonia Tsiftsi and Dimitrios Michail) + - Improved performance of filtered `AsSubgraph` creation (contributed by Kirill A. Korinsky) +- **version 1.5.2** (2-May-2023) + - Prepared release cycle 1.5.2: removed deprecated code, updated dependencies (contributed by Joris Kinable) + - Fixed NPE when no path exists in `DijkstraManyToManyShortestPaths` (contributed by Dimitrios Michail) + - Fixed NaN exception in case of a zero displacement in `FRLayoutAlgorithm2D` (contributed by Dimitrios Michail) + - Fixed Eclipse warnings in tests (contributed by Hannes Wellmann) + - Enforced naming conventions in checkstyle, allowing Latin chars (contributed by Hannes Wellmann) + - Bug fix for `TSPLIBImporter` which failed to parse burma14 due to multi-space delimiters (reported by Joris Kinable, contributed by Hannes Wellmann) + - Fixed succinct graph constructors in outgoing-only case (contributed by Sebastiano Vigna) + - Added support for custom names in vertices/edges collections in JSON I/O (contributed by Dimitrios Michail) + - Fixed assertion message in succinct graphs (contributed by Rostislav Svoboda) + - Made result ordering predictable in some algorithms (contributed by Dimitrios Michail) + - Added user guide for WebGraph and Sux4J adapters (contributed by Sebastiano Vigna) + - Github Actions maintenance (contributed by Szabolcs Besenyei) + - Added graph specifics strategy parameter for `DirectedAcyclicGraph` (contributed by Dariusz Dudek) + - Added explicit `NotDirectedAcyclicGraphException` (contributed by Kaiichiro Ota) + - Added explicit `GraphCycleProhibitedException` (contributed by Magnus Gunnarsson) + - Support custom vertex types in `DeltaSteppingShortestPath` (contributed by Semen Chudakov) + - Added path validation to `AllDirectedPaths` (contributed by Frans van Buul) + - Added `ApBetweennessCentrality` using new dependency on apfloat library (contributed by Dimitrios Michail) + - Added `UndirectedModularityMeasurer` (contributed by Dimitrios Michail) + - Added callback API to `DirectedSimpleCycles` interface (contributed by Sérgio Faria) + - Upgraded commons-text to 1.10.0 and antlr4-runtime to 4.10.1 (contributed by Rene Leonhardt) + - Centralized dependency version declarations (contributed by Rene Leonhardt) + - Updated all dependencies pre-release (contributed by Rene Leonhardt) + - Prevented XEE in `SimpleGEXFEventDrivenImporter` (contributed by DelfinSR) + +- **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. + - 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 + - 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 + - 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 + - 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. + - 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. + - 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 + - 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. + - 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, + - 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. + - 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 + - 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. + - 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. + - 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 + - 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`. + - 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. + - 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. + - 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. + - 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. + - 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. + - 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. \ No newline at end of file +- **version 0.4.0** (July-2003) : Initial public release. diff --git a/README.md b/README.md index f71d7c99921..fae5fd38a74 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,187 @@ -# JGraphtT +[![JGrapht Master build](https://github.com/jgrapht/jgrapht/actions/workflows/master-workflow.yaml/badge.svg)](https://github.com/jgrapht/jgrapht/actions/workflows/master-workflow.yaml) +[![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/maven-metadata/v?metadataUrl=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Forg%2Fjgrapht%2Fjgrapht-core%2Fmaven-metadata.xml&style=flat-square&label=snapshots&color=%2315252D)](https://central.sonatype.com/repository/maven-snapshots/org/jgrapht/jgrapht/maven-metadata.xml) +[![License](https://img.shields.io/badge/license-LGPL%202.1-blue.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) +[![License](https://img.shields.io/badge/license-EPL%202.0-blue.svg)](https://www.eclipse.org/legal/epl-2.0) +[![Language](http://img.shields.io/badge/language-java-brightgreen.svg)](https://www.java.com/) -Released: January, 2012

+# JGraphT -Written by [Barak Naveh](mailto:barak_naveh@users.sourceforge.net) and Contributors + -(C) Copyright 2003-2012, by Barak Naveh and Contributors. All rights +Released: May 2, 2023

+ +Written by [Barak Naveh](mailto:barak_naveh@users.sourceforge.net) and Contributors + +(C) Copyright 2003-2023, by Barak Naveh and Contributors. All rights reserved. -Please address all contributions, suggestions, and inquiries to the current project administrator [John Sichi](mailto:perfecthash@users.sf.net) +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 + -## Introduction ## +or the -JGraphT is a free Java class library that provides mathematical graph-theory objects and algorithms. It runs on Java 2 Platform (requires JDK 1.6 or later). + * Eclipse Public License (EPL) + -JGraphT is licensed under the terms of the GNU Lesser General Public License (LGPL). A copy of the [license](license-LGPL.txt) is included in the download. +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. -## Contents ## + 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 -- `licence-LGPL.txt` GNU Lesser General Public License +- `license-EPL.txt` Eclipse Public License 2.0 +- `license-LGPL.txt` GNU Lesser General Public License 2.1 - `javadoc/` Javadoc documentation -- `lib/` JGraphT libraries: -- `jgrapht-core-x.y.z.jar` core library -- `jgrapht-demo-x.y.z.jar` demo classes -- `jgrapht-ext-x.y.z.jar` extensions -- `jgrapht-x.y.z-combined.jar` all libraries rolled into one -- `jgraph-a.b.c.jar` JGraph dependency library +- `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 + - `apfloat-x.x.x.jar` Apfloat library + - `source/` complete source tree used to build this release -- `pom.xml` Maven project file ## 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:-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. -## Upgrading Versions ## +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/Users:-Projects-Using-JGraphT) us how you are using JGraphT, and [cite](https://github.com/jgrapht/jgrapht/wiki/Users:-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.2 +``` + +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.3-SNAPSHOT +``` + +and make sure the snapshot repository is enabled: + +```xml + + + Central Portal Snapshots + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots + + 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. +- **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 ## +## Documentation -A local copy of the Javadoc HTML files is included in this distribution. The latest version of these files is also available [on-line](http://www.jgrapht.org/javadoc). +A local copy of the Javadoc HTML files is included in the distribution. The latest version of these files is also available [online](http://www.jgrapht.org/javadoc). -## Dependencies ## +## Dependencies -- JGraphT requires JDK 1.6 or later to build. -- [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 `3.8.1`. -- [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. -- [JGraph](http://sourceforge.net/projects/jgraph) is a graph visualization and editing component. You need JGraph only if you want to create graph visualizations using the JGraphT-to-JGraph adapter. JGraph is licensed under the terms of the GNU Lesser General Public License (LGPL). -- [Touchgraph](http://sourceforge.net/projects/touchgraph) is a graph visualization and layout component. You need Touchgraph only if you want to create graph visualizations using the JGraphT-to-Touchgraph converter. Touchgraph is licensed under the terms of an Apache-style License. +- JGraphT requires JDK 11 or later to build starting with version 1.5.0. +- [JHeaps](https://www.jheaps.org/) is a library with priority queues. JHeaps is licensed under the terms of the Apache License, Version 2.0. +- [JUnit](https://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 Eclipse Public License - v 2.0. The JUnit tests included with JGraphT have been created using JUnit 5. +- [XMLUnit](https://www.xmlunit.org/) 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](https://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](https://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](jgrapht-guava) module. +- [Apache Commons Proper](https://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.](https://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](https://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](https://sux.di.unimi.it/) provides implementations of basic succinct data structures. Sux4j is only required by the jgrapht-unimi-dsi module. +- [jsap](https://www.martiansoftware.com/jsap/) provides a simple argument parser. Jsap is only required by the jgrapht-unimi-dsi module. +- [apfloat](https://www.apfloat.org/apfloat_java/) provides support for high performance arbitrary precision arithmetic. Apfloat is licensed under the terms of the MIT license. -## Online Resources ## +## Online Resources -The JGraphT website is at [http://www.jgrapht.org](http://www.jgrapht.org). You can use this site to: +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](http://wiki.jgrapht.org) set up for everyone in the JGraphT community to share information about the project. +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/Users:-Getting-Support) -Source code is hosted on [github](https://github.com/lingeringsocket/jgrapht). You can send contributions as pull requests there. +Source code is hosted on [github](https://github.com/jgrapht/jgrapht). You can send contributions as pull requests there. -## Your Improvements ## +## Your Improvements -If you add improvements to JGraphT please send them to us as pull requests on github. 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. +If you add improvements to JGraphT please send them to us as [pull requests on github](https://github.com/jgrapht/jgrapht/wiki/Dev-guide:-How-to-make-your-first-(code)-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 ## +## 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 \ No newline at end of file +[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 diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 00000000000..71606dd9119 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +jgrapht.org diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 00000000000..f8dd7464ecd --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem 'github-pages', group: :jekyll_plugins +gem 'jekyll-redirect-from', group: :jekyll_plugins diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 00000000000..4ddbc15a6c7 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,261 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.0.4.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.23.8) + concurrent-ruby (1.2.2) + dnsruby (1.61.9) + simpleidn (~> 0.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + execjs (2.8.1) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5) + forwardable-extended (2.6.0) + gemoji (3.0.1) + github-pages (228) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.3) + jekyll-avatar (= 0.7.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.15.1) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.26.0) + terminal-table (~> 1.4) + github-pages-health-check (1.17.9) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (~> 4.0) + public_suffix (>= 3.0, < 5.0) + typhoeus (~> 1.3) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.8.0) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + jekyll (3.9.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (>= 0.7, < 2) + 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.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.15.1) + 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-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + 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.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + 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.12.0) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.3.6) + mini_portile2 (2.8.1) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.18.0) + nokogiri (1.14.2) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (4.0.7) + racc (1.6.2) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.26.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + 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.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages + jekyll-redirect-from + +BUNDLED WITH + 2.3.9 diff --git a/docs/LGPL.html b/docs/LGPL.html new file mode 100644 index 00000000000..1794e5ab0e3 --- /dev/null +++ b/docs/LGPL.html @@ -0,0 +1,441 @@ + + + + + + + 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 +
+
+ + + + + diff --git a/docs/SinglePaged-LICENSE.txt b/docs/SinglePaged-LICENSE.txt new file mode 100644 index 00000000000..ba04e5d2460 --- /dev/null +++ b/docs/SinglePaged-LICENSE.txt @@ -0,0 +1,21 @@ +The 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. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000000..733af8fc181 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,27 @@ +permalink: /: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' diff --git a/docs/_includes/analytics.html b/docs/_includes/analytics.html new file mode 100755 index 00000000000..3b6227986f6 --- /dev/null +++ b/docs/_includes/analytics.html @@ -0,0 +1,15 @@ +{% if site.google_analytics_key %} + +{% endif %} diff --git a/docs/_includes/css/base.css b/docs/_includes/css/base.css new file mode 100644 index 00000000000..bdc957891ec --- /dev/null +++ b/docs/_includes/css/base.css @@ -0,0 +1,366 @@ +/* +* 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 */ diff --git a/docs/_includes/css/main.css b/docs/_includes/css/main.css new file mode 100644 index 00000000000..56aca0560a8 --- /dev/null +++ b/docs/_includes/css/main.css @@ -0,0 +1,357 @@ +html { 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; + } +} diff --git a/docs/_includes/css/skeleton.css b/docs/_includes/css/skeleton.css new file mode 100644 index 00000000000..b4e0a558413 --- /dev/null +++ b/docs/_includes/css/skeleton.css @@ -0,0 +1,91 @@ + +/* -----------------------------------*/ +/* ----- 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; + } diff --git a/docs/_includes/footer.md b/docs/_includes/footer.md new file mode 100755 index 00000000000..5007081fd64 --- /dev/null +++ b/docs/_includes/footer.md @@ -0,0 +1,7 @@ + + +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. diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 00000000000..02972a56842 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,22 @@ + + + + + + + + +< +{% seo %} + + + +
+ JGraphT Logo + {{ content }} +
+ + + {% include analytics.html %} + + diff --git a/docs/_posts/2000-01-01-intro.md b/docs/_posts/2000-01-01-intro.md new file mode 100755 index 00000000000..d64b7aa649c --- /dev/null +++ b/docs/_posts/2000-01-01-intro.md @@ -0,0 +1,49 @@ +--- +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 + + diff --git a/docs/_posts/2000-01-02-jumpstart.md b/docs/_posts/2000-01-02-jumpstart.md new file mode 100755 index 00000000000..91dd6ed132e --- /dev/null +++ b/docs/_posts/2000-01-02-jumpstart.md @@ -0,0 +1,32 @@ +--- +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.2</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! diff --git a/docs/_posts/2000-01-03-docs.md b/docs/_posts/2000-01-03-docs.md new file mode 100755 index 00000000000..4f0c24862cd --- /dev/null +++ b/docs/_posts/2000-01-03-docs.md @@ -0,0 +1,15 @@ +--- +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) diff --git a/docs/_posts/2000-01-04-download.md b/docs/_posts/2000-01-04-download.md new file mode 100755 index 00000000000..832e666ffec --- /dev/null +++ b/docs/_posts/2000-01-04-download.md @@ -0,0 +1,30 @@ +--- +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: + +
+
+
.zip
+
+
+
.tar.gz
+
+
+ +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. + diff --git a/docs/_posts/2000-01-05-community.md b/docs/_posts/2000-01-05-community.md new file mode 100644 index 00000000000..2518f65102a --- /dev/null +++ b/docs/_posts/2000-01-05-community.md @@ -0,0 +1,20 @@ +--- +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) diff --git a/docs/_posts/2000-01-06-news.md b/docs/_posts/2000-01-06-news.md new file mode 100644 index 00000000000..df9cb04942a --- /dev/null +++ b/docs/_posts/2000-01-06-news.md @@ -0,0 +1,20 @@ +--- +title: "News" +bg: orange +color: white +fa-icon: exclamation +style: center +--- +## **2-May-2023:** Release 1.5.2 is now available! + +## **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. diff --git a/docs/_posts/2000-01-07-ack.md b/docs/_posts/2000-01-07-ack.md new file mode 100644 index 00000000000..cfd81624c4f --- /dev/null +++ b/docs/_posts/2000-01-07-ack.md @@ -0,0 +1,37 @@ +--- +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)! + +
+ +
diff --git a/docs/combo.css b/docs/combo.css new file mode 100644 index 00000000000..c64e1a6b694 --- /dev/null +++ b/docs/combo.css @@ -0,0 +1,5 @@ +--- +--- +{% include css/base.css %} +{% include css/skeleton.css %} +{% include css/main.css %} diff --git a/docs/guide-templates/GuavaAdapter.md b/docs/guide-templates/GuavaAdapter.md new file mode 100644 index 00000000000..66d9a6e8d75 --- /dev/null +++ b/docs/guide-templates/GuavaAdapter.md @@ -0,0 +1,29 @@ +--- +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). diff --git a/docs/guide-templates/HelloJGraphT.md b/docs/guide-templates/HelloJGraphT.md new file mode 100644 index 00000000000..0f7f3a4d6df --- /dev/null +++ b/docs/guide-templates/HelloJGraphT.md @@ -0,0 +1,9 @@ +--- +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) +``` diff --git a/docs/guide-templates/LabeledEdges.md b/docs/guide-templates/LabeledEdges.md new file mode 100644 index 00000000000..0b68f6e7e88 --- /dev/null +++ b/docs/guide-templates/LabeledEdges.md @@ -0,0 +1,47 @@ +--- +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) diff --git a/docs/guide-templates/Sux4JImplementations.md b/docs/guide-templates/Sux4JImplementations.md new file mode 100644 index 00000000000..18fe1ab7155 --- /dev/null +++ b/docs/guide-templates/Sux4JImplementations.md @@ -0,0 +1,54 @@ +--- +title: Sux4J-Based Implementations +--- + +# {{ page.title }} + +[Sux4J](https://sux4j.di.unimi.it/) is a library containing +implementations of [succinct data structures](https://en.wikipedia.org/wiki/Succinct_data_structure) +in Java. Such structures can be used to store graphs in a very compact form. For example, +the memory footprint of the [English Wikipedia graph in 2013](http://law.di.unimi.it/webdata/enwiki-2013/) +would be a few gigabytes in a trivial object-based representation, it is 1.6GB in JGraphT's +[sparse representation](https://jgrapht.org/javadoc/org.jgrapht.opt/org/jgrapht/opt/graph/sparse/SparseIntDirectedGraph.html), +but it is just 500MB in a succinct representation. The denser the graph, the more +marked these differences will be. + +The implementations in the package +[org.jgrapht.sux4j](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/package-summary.html) +make it possible to use succinct representation of graphs in JGraphT. +The implementations are serializable, and you can download ready-made +graphs in this form from the [LAW web site](http://law.di.unimi.it/datasets.php). + +The typical use case for these adapters is: + +- You need a compact format. +- You have less than 231 vertices and less than 231 edges. +- You have metadata associated with the vertices and with the edges. +- Optionally, you need fast [adjacency tests](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#containsEdge%28V,V%29). + +Such metadata can be easily stored in an array or list indexed by the vertices or +the edges. If you have metadata on the vertices only, or if your number +of vertices or edges does not satisfy the limitations above, a [WebGraph +adapter](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/webgraph/package-summary.html) +might be more appropriate. A separate [guide](WebGraphAdapters) is available for +WebGraph adapters. + +The two main implementations are [`SuccinctDirectedGraph`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/SuccinctDirectedGraph.html) +and [`SuccinctUndirectedGraph`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/SuccinctUndirectedGraph.html). +They both use pairs to represent edges, but you can easily [map edges](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/SuccinctDirectedGraph.html#getEdgeFromIndex%28long%29) +in a contiguous segment of integers starting from zero; the mapping is reasonably fast. + +Note that one of the benefits of the succinct representation used by these classes is that +[adjacency tests](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#containsEdge%28V,V%29) are very fast. + +If you need, however, an implementation whose vertex and edge type is +`Integer` (for example, for usage with [Python +bindings](https://pypi.org/project/jgrapht/)), there are classes +[`SuccinctIntDirectedGraph`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/SuccinctIntDirectedGraph.html) +and +[`SuccinctIntUndirectedGraph`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/SuccinctIntUndirectedGraph.html). +These classes, however, are fairly slow due to the necessity of +continuously remapping edges from pairs to indices. We suggest that you use them +only in the directed case and for outgoing arcs. However, in some cases +they might provide the only representation of this type that is small +enough to be loaded in main memory. diff --git a/docs/guide-templates/UserOverview.md b/docs/guide-templates/UserOverview.md new file mode 100644 index 00000000000..dc7454dff5b --- /dev/null +++ b/docs/guide-templates/UserOverview.md @@ -0,0 +1,471 @@ +--- +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. + +### Adapters for Very Large Graphs + +If you are trying to run algorithms over very large graphs, the +default JGraphT representations may eat up too much of your main +memory. Instead, you can use the adapters provided for +[WebGraph](WebGraphAdapters) or +[succinct graphs](Sux4JImplementations) (via Sux4J). + +### 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). diff --git a/docs/guide-templates/VertexAndEdgeTypes.md b/docs/guide-templates/VertexAndEdgeTypes.md new file mode 100644 index 00000000000..79337d43607 --- /dev/null +++ b/docs/guide-templates/VertexAndEdgeTypes.md @@ -0,0 +1,304 @@ +--- +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. diff --git a/docs/guide-templates/WebGraphAdapters.md b/docs/guide-templates/WebGraphAdapters.md new file mode 100644 index 00000000000..8ea5fe639c3 --- /dev/null +++ b/docs/guide-templates/WebGraphAdapters.md @@ -0,0 +1,57 @@ +--- +title: WebGraph Adapters +--- + +# {{ page.title }} + +[WebGraph](https://webgraph.di.unimi.it/) is a framework for storing and +accessing graphs (and in particular web graphs) in a compressed form, +making it possible to load and access very large graphs with a moderate +amount of memory. You can download ready-made graphs in this form from the +[LAW web site](http://law.di.unimi.it/datasets.php), or compress your own +graphs using the instructions provided in the [package overview](https://webgraph.di.unimi.it/docs/). + +For example, the memory footprint of a [snapshot of web sites from Indochina in 2004](http://law.di.unimi.it/webdata/indochina-2004/) +with 200 million edges would be a few gigabytes in a trivial representation, it is 260MB in JGraphT's +[sparse representation](https://jgrapht.org/javadoc/org.jgrapht.opt/org/jgrapht/opt/graph/sparse/SparseIntDirectedGraph.html), +but it is just 59MB in WebGraph. + +The adapters in the package +[org.jgrapht.webgraph](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/webgraph/package-summary.html) +make it possible to use graphs in WebGraph format in JGraphT. + +The typical use case for these adapters is: + +- You need a compact format (vertices will be just contiguous integers starting from zero). +- The type of graph you are storing is compressible. +- You have metadata associated with the vertices, but not with the arcs. + +Such metadata can be easily stored in an array indexed by the vertices, +or possibly by a [`fastutil` big array](https://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/BigArrays.html) +if you have more than 231 vertices (lists and [big lists](https://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/BigList.html) are another option). + +If you need to associate metadata with the arcs, and manage the graph in a +compact format, a succinct representation from the package +[org.jgrapht.sux4j](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/sux4j/package-summary.html) +might be more appropriate, as those representation associate with +each edge an integer in a contiguous range starting from zero. +A separate [guide](Sux4JImplementations) is available for +succinct graph adapters. + +WebGraph has two versions: the standard version manages graph with +at most 231 vertices, whereas the big version manages graphs with +at most 263 vertices. For each version, there is a directed +adapter and an undirected adapter. The Javadoc documentation of +[`ImmutableDirectedGraphAdapter`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/webgraph/ImmutableDirectedGraphAdapter.html) +and [`ImmutableUndirectedGraphAdapter`](https://jgrapht.org/javadoc/org.jgrapht.unimi.dsi/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapter.html) +contain examples of how to load graphs in webgraph and use them +to build an adapter. The big adapters work in the same way. + +Note that WebGraph has two main representations: +[`BVGraph`](https://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/BVGraph.html) +uses compression techniques that work well with web graphs; +[`EFGraph`](https://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/EFGraph.html) +uses [succinct techniques](https://en.wikipedia.org/wiki/Succinct_data_structure), +which might be more useful with less repetitive graphs such as social graphs. In particular, `EFGraph` implements a +[fast adjacency test](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#containsEdge%28V,V%29). +You should choose the representation that better suits your data and access primitives. diff --git a/docs/guide/.gitignore b/docs/guide/.gitignore new file mode 100644 index 00000000000..4052ed8dbe4 --- /dev/null +++ b/docs/guide/.gitignore @@ -0,0 +1,5 @@ +# 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 diff --git a/docs/guide/hello.png b/docs/guide/hello.png new file mode 100644 index 00000000000..4c0e131fc4e Binary files /dev/null and b/docs/guide/hello.png differ diff --git a/docs/img/apple-touch-icon.png b/docs/img/apple-touch-icon.png new file mode 100644 index 00000000000..14a8519aed0 Binary files /dev/null and b/docs/img/apple-touch-icon.png differ diff --git a/docs/img/bgnoise.png b/docs/img/bgnoise.png new file mode 100644 index 00000000000..2912433c798 Binary files /dev/null and b/docs/img/bgnoise.png differ diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico new file mode 100644 index 00000000000..bfc2884f0e0 Binary files /dev/null and b/docs/img/favicon.ico differ diff --git a/docs/img/logo.png b/docs/img/logo.png new file mode 100644 index 00000000000..d8535e2b9e5 Binary files /dev/null and b/docs/img/logo.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000000..5e9a16b7cef --- /dev/null +++ b/docs/index.html @@ -0,0 +1,71 @@ +--- +--- + + + + + + {{ 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 %} + + + + diff --git a/docs/mathjax/mathjaxConfig.js b/docs/mathjax/mathjaxConfig.js new file mode 100644 index 00000000000..1358e178bb9 --- /dev/null +++ b/docs/mathjax/mathjaxConfig.js @@ -0,0 +1,7 @@ +MathJax.Hub.Config({ + tex2jax: { + inlineMath: [['$','$'], ['\\(','\\)']], + processEscapes: true + } +}); +MathJax.Ajax.loadComplete("https://jgrapht.org/mathjax/mathjaxConfig.js"); diff --git a/docs/site.js b/docs/site.js new file mode 100644 index 00000000000..4746f936367 --- /dev/null +++ b/docs/site.js @@ -0,0 +1,95 @@ + +$.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"); + }); + } + }); + +}); + diff --git a/docs/visualizations.html b/docs/visualizations.html new file mode 100644 index 00000000000..155c2109fad --- /dev/null +++ b/docs/visualizations.html @@ -0,0 +1,279 @@ + + + + + + + + + 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 +
+
+ + + + + diff --git a/etc/ReleaseProcess.md b/etc/ReleaseProcess.md new file mode 100644 index 00000000000..5b581db6033 --- /dev/null +++ b/etc/ReleaseProcess.md @@ -0,0 +1,29 @@ +# 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, merge with master branch. Locally, switch back to the master branch and perform a `git pull upstream master` to ensure that the local and upstream master are synced after merging the code formatting changes. +1. Run `mvn -Dmaven.artifact.threads=1 -DskipTests 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). Make sure to login to the old https://oss.sonatype.org/, and NOT https://s01.oss.sonatype.org/ or you will get a `Incorrect username, password or no permission to use the Nexus User Interface.` error! +1. Before continuing, restart from a fresh clone to make sure your workspace is clean, and checkout the release branch there by performing a `git checkout jgrapht-x.y.z`. 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 -DskipTests 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. Make sure you are on the `master` branch, NOT on the `gh-pages` branch! (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.) which can be found under `./docs/_posts/`. 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` diff --git a/etc/build.properties.template b/etc/build.properties.template deleted file mode 100644 index 6d37a9a31ba..00000000000 --- a/etc/build.properties.template +++ /dev/null @@ -1,53 +0,0 @@ -# ========================================== -# JGraphT : a free Java graph-theory library -# ========================================== -# -# Project Info: http://jgrapht.sourceforge.net/ -# Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) -# -# (C) Copyright 2003-2006, by Barak Naveh and Contributors. -# -# 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. -# -# ~~~~~~~~~ -# etc/build.properties -# ~~~~~~~~~ -# -# Original Author: John V. Sichi -# Contributor(s): - -# -# $Id$ -# -# Changes -# ~~~~~~~ -# 31-July-2005 : Initial revision (JVS); -# 01-July-2006 : Update for version 0.7 (JVS); -# ~~~~~~~ -# -# etc/build.properties is used to customize the JGraphT build. -# To use it, uncomment the settings below for the attributes you want -# to customize, then change the values to your site-specifics. -# The ant buildfile will override its own defaults with any -# uncommented values it sees here. - -# NOTE: If you are building JGraphT directly from Subversion rather than from -# a distribution, create etc/build.properties by making a copy of -# etc/build.properties.template. (The template file should only be -# changed by developers who are enhancing the build process -# with customizability.) - -# ~~~~~~~~~~~~~~~~~ -# End build.properties -# ~~~~~~~~~~~~~~~~~ diff --git a/etc/checkstyle_suppressions.xml b/etc/checkstyle_suppressions.xml new file mode 100644 index 00000000000..a57ab41f5c5 --- /dev/null +++ b/etc/checkstyle_suppressions.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/etc/codeFormatter.sh b/etc/codeFormatter.sh new file mode 100755 index 00000000000..5222940eac1 --- /dev/null +++ b/etc/codeFormatter.sh @@ -0,0 +1,37 @@ +#!/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 + +# This works well with Eclipse Neon; later Eclipse versions don't work. Note that Eclipse Neon only works with java 8; newer java version cause problems, hence the explicit java_path definition below. + +#Path to eclipse. Needs eclipse Neon or newer. +eclipse_path=/opt/eclipse/eclipse +#Path to Java +java_path=/usr/lib/jvm/java-8-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 diff --git a/etc/downloadJavadoc.sh b/etc/downloadJavadoc.sh new file mode 100755 index 00000000000..6f45d5a40fe --- /dev/null +++ b/etc/downloadJavadoc.sh @@ -0,0 +1,39 @@ +#!/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 diff --git a/etc/eclipse-formatter-settings.xml b/etc/eclipse-formatter-settings.xml index 99685090b3e..2a820857327 100644 --- a/etc/eclipse-formatter-settings.xml +++ b/etc/eclipse-formatter-settings.xml @@ -1,246 +1,365 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/expandMarkdown.sh b/etc/expandMarkdown.sh new file mode 100755 index 00000000000..d635264b3c2 --- /dev/null +++ b/etc/expandMarkdown.sh @@ -0,0 +1,30 @@ +#!/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 diff --git a/etc/graph-links.html b/etc/graph-links.html deleted file mode 100644 index 62117b5e720..00000000000 --- a/etc/graph-links.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - Graph Links - - - - - -

Graph Links

- - - -
-

In this page contains a list of links of graph-related sites. The links are -not sorted in any particular order or significance.

- -

Link Hubs

- -

-http://pharos.inria.fr/Java/query.jsp?cids=c_2089

- -

 

- -

Java Graph Links - Free

- -

http://gef.tigris.org/ - -a graph editing library + editor.

-

http://jgraph.sf.net/ - a graph editing -component. includes an editor and some layout support.

-

http://jdigraph.sourceforge.net/ -- a Java library for representing and working with directed graphs and paths.

-

-http://touchgraph.sourceforge.net/ - provides a -hands-on way to visualize networks of interrelated information.

-

-http://www.cs.rpi.edu/projects/pb/jgb/ - classes which implement -graph-theory objects and some algorithms (euler, longestpath etc.).

-

-http://www.research.att.com/~john/Grappa/ - a port of a subset of GraphViz -to Java.

-

http://www.cs.brown.edu/cgc/jdsl/ -- graph traversals, shortest path, minimum spanning -tree etc.

-

-http://www.bluemarsh.com/java/graph/ - GraphMaker is a Java -application for graphically illustrating graph algorithms, such as breadth-first -searches and minimum-spanning trees.

-

-http://www.caida.org/tools/visualization/libsea/ - LibSea is both a file -format and a Java library for representing large directed graphs on disk and in -memory.

-

http://wilma.sourceforge.net/ - a -Java library which allows other programs to create animated 3d graph -visualisations.

-

http://jung.sourceforge.net/ - a -library that provides a common and extendible language for the modeling, -analysis, and visualization of data that can be represented as a graph or -network.

-

http://www.math.tu-berlin.de/jGABL/

-

 

- -

Java Graph Links - Commercial

- -

http://www.yworks.de - company providing -graph-related products: editor, algorithmic library and more. not free. not open -source.

-

http://www.nwoods.com/go/jgo.htm -- a graphics library to build custom interactive diagrams.

-

 

- - -

Non-Java Graph Links

- -

-http://www.boost.org/libs/graph/doc/ - The Boost graph library.

- -

-http://www.graphviz.org - a collection of -tools for manipulating graph structures and generating graph layouts.

- - - -

- -http://www.mpi-sb.mpg.de/LEDA/index.html -

- - - -

-http://pigale.sourceforge.net/ - a -tool for graph editing, layout and investigation.
-

- - - -

-
-

- - - -
- - - - - - - - -
Valid HTML 4.01! - © Copyright 2003, by Barak Naveh and Contributors. All rights reserved.SourceForge.net Logo
-
- - - - \ No newline at end of file diff --git a/etc/header-boilerplate.txt b/etc/header-boilerplate.txt new file mode 100644 index 00000000000..489c85507cb --- /dev/null +++ b/etc/header-boilerplate.txt @@ -0,0 +1,17 @@ +/* + * (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 + */ diff --git a/etc/jgrapht_checks.xml b/etc/jgrapht_checks.xml new file mode 100644 index 00000000000..e9de4b41512 --- /dev/null +++ b/etc/jgrapht_checks.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/licenses/antlr-license.txt b/etc/licenses/antlr-license.txt new file mode 100644 index 00000000000..2042d1bda6c --- /dev/null +++ b/etc/licenses/antlr-license.txt @@ -0,0 +1,52 @@ +[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. diff --git a/etc/licenses/apache-license-2.0.txt b/etc/licenses/apache-license-2.0.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/etc/licenses/apache-license-2.0.txt @@ -0,0 +1,202 @@ + + 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. diff --git a/etc/licenses/jgraphx-license.txt b/etc/licenses/jgraphx-license.txt new file mode 100644 index 00000000000..7fdadcf7a9b --- /dev/null +++ b/etc/licenses/jgraphx-license.txt @@ -0,0 +1,24 @@ +Copyright (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. diff --git a/etc/licenses/mit-license.txt b/etc/licenses/mit-license.txt new file mode 100644 index 00000000000..14e2e075df0 --- /dev/null +++ b/etc/licenses/mit-license.txt @@ -0,0 +1,19 @@ +Copyright (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. diff --git a/etc/logo/jgrapht-logo-black.ai b/etc/logo/jgrapht-logo-black.ai new file mode 100644 index 00000000000..8058baae034 --- /dev/null +++ b/etc/logo/jgrapht-logo-black.ai @@ -0,0 +1,290 @@ +%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 +%%EOF \ No newline at end of file diff --git a/etc/logo/jgrapht-logo-black.eps b/etc/logo/jgrapht-logo-black.eps new file mode 100644 index 00000000000..76062f9bf2c Binary files /dev/null and b/etc/logo/jgrapht-logo-black.eps differ diff --git a/etc/logo/jgrapht-logo-black.jpg b/etc/logo/jgrapht-logo-black.jpg new file mode 100644 index 00000000000..9929a76bfb7 Binary files /dev/null and b/etc/logo/jgrapht-logo-black.jpg differ diff --git a/etc/logo/jgrapht-logo-black.pdf b/etc/logo/jgrapht-logo-black.pdf new file mode 100644 index 00000000000..9649234409d Binary files /dev/null and b/etc/logo/jgrapht-logo-black.pdf differ diff --git a/etc/logo/jgrapht-logo-black.png b/etc/logo/jgrapht-logo-black.png new file mode 100644 index 00000000000..0dbff4cd824 Binary files /dev/null and b/etc/logo/jgrapht-logo-black.png differ diff --git a/etc/logo/jgrapht-logo-black.psd b/etc/logo/jgrapht-logo-black.psd new file mode 100644 index 00000000000..971b9eafa6c Binary files /dev/null and b/etc/logo/jgrapht-logo-black.psd differ diff --git a/etc/logo/jgrapht-logo-blue.ai b/etc/logo/jgrapht-logo-blue.ai new file mode 100644 index 00000000000..e0b8721f3ed --- /dev/null +++ b/etc/logo/jgrapht-logo-blue.ai @@ -0,0 +1,289 @@ +%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 +%%EOF \ No newline at end of file diff --git a/etc/logo/jgrapht-logo-blue.eps b/etc/logo/jgrapht-logo-blue.eps new file mode 100644 index 00000000000..312244c4b3c Binary files /dev/null and b/etc/logo/jgrapht-logo-blue.eps differ diff --git a/etc/logo/jgrapht-logo-blue.jpg b/etc/logo/jgrapht-logo-blue.jpg new file mode 100644 index 00000000000..301ebffb70f Binary files /dev/null and b/etc/logo/jgrapht-logo-blue.jpg differ diff --git a/etc/logo/jgrapht-logo-blue.pdf b/etc/logo/jgrapht-logo-blue.pdf new file mode 100644 index 00000000000..99387231b72 Binary files /dev/null and b/etc/logo/jgrapht-logo-blue.pdf differ diff --git a/etc/logo/jgrapht-logo-blue.png b/etc/logo/jgrapht-logo-blue.png new file mode 100644 index 00000000000..390a04ab351 Binary files /dev/null and b/etc/logo/jgrapht-logo-blue.png differ diff --git a/etc/logo/jgrapht-logo-blue.psd b/etc/logo/jgrapht-logo-blue.psd new file mode 100644 index 00000000000..2241de5b450 Binary files /dev/null and b/etc/logo/jgrapht-logo-blue.psd differ diff --git a/etc/logo/jgrapht-logo-transparent-cropped-javadocheader.png b/etc/logo/jgrapht-logo-transparent-cropped-javadocheader.png new file mode 100644 index 00000000000..78e8b96b5df Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent-cropped-javadocheader.png differ diff --git a/etc/logo/jgrapht-logo-transparent-cropped.png b/etc/logo/jgrapht-logo-transparent-cropped.png new file mode 100644 index 00000000000..d8535e2b9e5 Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent-cropped.png differ diff --git a/etc/logo/jgrapht-logo-transparent.ai b/etc/logo/jgrapht-logo-transparent.ai new file mode 100644 index 00000000000..9923083c864 --- /dev/null +++ b/etc/logo/jgrapht-logo-transparent.ai @@ -0,0 +1,289 @@ +%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 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 +%%EOF \ No newline at end of file diff --git a/etc/logo/jgrapht-logo-transparent.eps b/etc/logo/jgrapht-logo-transparent.eps new file mode 100644 index 00000000000..c42b45e502b Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent.eps differ diff --git a/etc/logo/jgrapht-logo-transparent.jpg b/etc/logo/jgrapht-logo-transparent.jpg new file mode 100644 index 00000000000..f1092b85cb2 Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent.jpg differ diff --git a/etc/logo/jgrapht-logo-transparent.pdf b/etc/logo/jgrapht-logo-transparent.pdf new file mode 100644 index 00000000000..b2c08c55a92 Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent.pdf differ diff --git a/etc/logo/jgrapht-logo-transparent.png b/etc/logo/jgrapht-logo-transparent.png new file mode 100644 index 00000000000..69262aa6801 Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent.png differ diff --git a/etc/logo/jgrapht-logo-transparent.psd b/etc/logo/jgrapht-logo-transparent.psd new file mode 100644 index 00000000000..f83f8039a21 Binary files /dev/null and b/etc/logo/jgrapht-logo-transparent.psd differ diff --git a/etc/org.eclipse.jdt.core.prefs b/etc/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..d42c55426f9 --- /dev/null +++ b/etc/org.eclipse.jdt.core.prefs @@ -0,0 +1,362 @@ +eclipse.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 diff --git a/etc/prepareDocs.sh b/etc/prepareDocs.sh new file mode 100755 index 00000000000..b2a5be4980c --- /dev/null +++ b/etc/prepareDocs.sh @@ -0,0 +1,11 @@ +#!/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/reports/apidocs ${GITHUB_WORKSPACE}/docs/javadoc-SNAPSHOT diff --git a/etc/release-process.html b/etc/release-process.html deleted file mode 100644 index 37aa14c8732..00000000000 --- a/etc/release-process.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - JGraphT Release Process - - - - -

JGraphT Release Process


- -
    - -
  1. Update the new version number and SVN tag in the build.xml file. - -
  2. Review the README.md, HISTORY.md, CONTRIBUTORS.md, and META-INF/MANIFEST.MF files and update: -
      -
    • Version -
    • Dependencies -
    • Release notes -
    • Contributors -
    - -
  3. Review/update the bug/feature trackers 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. - -
  4. Bring dependent libraries (e.g. JGraph) up to date and update -lib/lib-readme.txt accordingly. - -
  5. Run ant to build the javadoc and make sure it is generated without -errors/warnings. Fix where necessary. Make sure Eclipse build is -warning-free. - -
  6. We used to run Checkstyle globally to make a "code quality review"; -we may bring this back, and/or add PMD/FindBugs, and Emma for -code coverage. - -
  7. Run all the JUnit tests. Fix where necessary. - -
  8. Run jalopy to reformat the code. - -
  9. Commit all work to SVN. - -
  10. Add a SVN tag using the convention: ver_x_y_z (e.g., the tag ver_0_7_1 -denotes Version 0.7.1). The command for this is something like -svn copy https://jgrapht.svn.sourceforge.net/svnroot/jgrapht/trunk - https://jgrapht.svn.sourceforge.net/svnroot/jgrapht/tags/ver_0_7_1 - -m "Tag for the 0.7.1 release of JGraphT" - -
  11. -At this stage, the release is complete in SVN and only a distribution -is needed to be built. The distribution is built from SVN to ensure -that whatever gets released is reproducible later. - -
  12. The ant build.xml has a hack that allows specifying a SVN tag. -You just need to invoke: - -
    
    -      ant -Dsvntag=ver_0_7_1 release
    -
    - -It will checkout from SVN the tagged version 0.7.1, and build a -distribution for it. - -
  13. Upload the release to SF and add it using the File Release System. - -
  14. Update the website with the latest javadocs. - -
  15. Update the website with links to the new downloads, version numbers, -etc. This involves uploading web/index.html (at least). - -
  16. Announce the new version in the wiki and mailing lists; also click -the File Release System notification button. - -
  17. Update and commit the version number in build.xml again to reflect -the beginning of development for the next version. - -
- -
- - - - - - - - - -
Valid HTML 4.01! Copyright 2005, by Barak Naveh - and Contributors. All rights reserved.SourceForge.net Logo

- - diff --git a/etc/snapshot-settings.xml b/etc/snapshot-settings.xml new file mode 100644 index 00000000000..12a73478ec8 --- /dev/null +++ b/etc/snapshot-settings.xml @@ -0,0 +1,12 @@ + + + + central + ${CENTRAL_USER} + ${CENTRAL_PASSWORD} + + + diff --git a/etc/triemax-jalopy-settings.xml b/etc/triemax-jalopy-settings.xml deleted file mode 100644 index 17ade63923d..00000000000 --- a/etc/triemax-jalopy-settings.xml +++ /dev/null @@ -1,1021 +0,0 @@ - - - - - -
- false - - 1 - false - // End $file.name$ - true -
- - - - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - - - false - true - true - - true - - - true - true - true - false - false - false - false - true - false - true - false - false - false - true - - - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - - - false - false - false - false - false - - - true - true - true - - - false - false - false - true - false - false - - - false - false - true - - false - - true - true - - - true - true - - - true - true - true - true - true - false - true - true - false - - true - - false - - - true - - - - - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - - - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - true - - - false - false - true - - true - true - - true - true - false - true - false - true - true - true - true - - - true - - - false - false - false - false - false - - - false - false - - - true - true - - true - - - true - true - true - - 1 - - - false - false - false - false - false - false - false - - - false - false - - - false - false - - - false - false - - - - - false - false - false - - - false - false - false - false - false - false - - - 1 - 0 - false - 1 - false - - - 0 - 1 - false - - - false - 25 - false - false - false - false - false - false - false - false - - - false - false - true - false - false - false - false - false - false - false - - - false - 1 - 0 - - - 0 - false - 1 - - - true - true - true - false - true - - true - - 1 - 0 - false - false - 1 - - - false - true - true - - - 0 - false - 1 - - - 0 - 1 - false - - - - false - true - false - false - false - false - false - - - - false - true - false - false - - - - false - false - - - false - - false - - - false - false - false - - - 0 - 4 - 1 - 8 - 1 - 4 - 0 - -1 - 80 - -1 - 4 - -1 - 1 - 1 - - - false - false - true - false - - true - true - true - false - true - standard - - false - true - - - - - - - - - - - - - - - - - - - /** TODO: doc */ - - - - - - - - - - - - - - - - - - false - false - true - false - 1 - - - @testcase|@post|@pre|@sql.92|@sql.99|@sql.2003 - - - - false - false - false - true - false - - - - false - false - false - false - false - false - - - false - false - false - false - false - false - - false - false - false - false - false - - - - - true - true - false - true - true - - false - false - false - - true - false - 80 - - false - false - - 0 - 0 - true - 0 - false - true - - false - 0 - - false - false - true - false - false - - false - false - - false - - false - - - Enums - Annotations - Static fields/initializers - Enum constants - Ordinary methods - Methods - Instance initializers - Constructors - Inner Interfaces - Inner Classes - Instance fields - - false - false - 80 - - false - true - - - false - //~ ${fill.character}* - - - false - false - - - - - false - false - - - false - false - - - - false - - false - - - - 80 - 20 - true - false - - -1 - - true - false - - - - - - true - false - level|name - true - - - true - true - - - false - - true - false - false - level|name|count|bean|custom|regex - true - - - false - level|count|default|copy - false - false - true - true - - - false - foo|getFoo|isFoo|setFoo - - true - - true - name|count - false - true - - - level|name|type - true - false - true - true - - static|enum|enumConstant|field|initializer|constructor|method|interface|class|annotation - - false - public|protected|friendly|private|abstract|static|final|native - true - true - true - true - true - true - false - - - level|name - true - false - true - - - true - level|name - true - false - - - - annotation|public|protected|private|abstract|static|final|synchronized|transient|volatile|native|strictfp - false - - - - - 0 - 0 - 0 -
0
- - 1 - - 0 - 1 -
1
- 1 - 0 - 1 - 1 - 1 - 2 - 1 - 1 -
- - 0 - 0 - 0 - 0 - - 1 - 1 - 1 - 1 - -
1
- 0 - 1 - 0 -
1
- 0 - 0 -
0
- 0 -
- false - 1 - false - 1 - false -
- - - true - true - true - - false - - 80 - false - true - true - true - true - true - - - true - true - false - true - - true - true - false - false - false - true - false - - false - - - true - true - true - - true - true - true - - false - false - - true - true - true - false - - true - false - - - - - false - false - - false - false - false - false - false - - false - false - false - false - false - 0 - false - false - - - true - false - false - true - false - true - false - - - false - false - false - - - - false - - - false - false - false - false - - - false - - - - - false - false - - false - false - false - - false - false - false - false - false - - true - false - ^(is)[A-Z]\w+ - false - true - false - - - false - false - - false - false - - - - none - 2 - *:0 - - false - true - collapse - repository - -
- false - - Copyright - false - true - 0 -
- - true - false - true - - - false - - -
- - - - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - [a-z][\w]+ - - - [a-zA-Z][\w]+ - [a-z][\w]+ - [a-z]\w+ - [a-z][\w]+ - [a-z][\w]+ - [a-z]\w+ - [a-z][\w]+ - [a-zA-Z][\w]+ - [a-zA-Z][\w]+ - [a-z][\w]+ - [a-zA-Z][\w]+ - [a-z][\w]+ - [a-z]\w+ - [a-z]\w+ - [a-z][\w]+ - [a-z][\w]+ - - [A-Z][a-zA-Z0-9]+ - - [a-z]\w* - [a-z]\w* - [a-z][\w]* - - \w+ - - [a-z][\w]+ - \w+ - [a-z][\w]+ - - - [A-Z][a-zA-Z0-9]+ - [A-Z][a-zA-Z0-9]+ - [A-Z][a-zA-Z0-9]+ - - [a-z]+(?:\.[a-z]+)* - - - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - false - - false - - - log - false - run.log - - - - 30000 - 30000 - 30000 - 30000 - 30000 - 30000 - 30000 - 30000 - - true - - - - 15 - - -
- - - M/d/yy - en - h:mm a - US - - - false - - - Java Coding Convention For JGraphT - file:/home/jvs/open/jgrapht/etc/triemax-jalopy-settings.xml - - false - JGraphT - - - false - DEFAULT - true - false - false - false - 1 - - - 1 - bak - - - - none - - - true - false - - - - 29 - -
-
- diff --git a/etc/updateCopyRightYear.sh b/etc/updateCopyRightYear.sh new file mode 100755 index 00000000000..55b3fce79a9 --- /dev/null +++ b/etc/updateCopyRightYear.sh @@ -0,0 +1,29 @@ +#!/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 diff --git a/etc/updateHeader.sh b/etc/updateHeader.sh new file mode 100755 index 00000000000..205909121d9 --- /dev/null +++ b/etc/updateHeader.sh @@ -0,0 +1,37 @@ +#!/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 diff --git a/jgrapht-core/pom.xml b/jgrapht-core/pom.xml index ec3e3b1874c..5f2ae95d119 100644 --- a/jgrapht-core/pom.xml +++ b/jgrapht-core/pom.xml @@ -1,18 +1,124 @@ - - 4.0.0 - - net.sf.jgrapht - jgrapht - 0.8.3-SNAPSHOT - - jgrapht-core - JGraphT - Core - - - junit - junit - - + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + jgrapht-core + JGraphT - Core + + ${project.parent.basedir} + slow,optional + + + + 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 + + + org.apfloat + apfloat + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + org.hamcrest + hamcrest + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + 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 + + + --add-exports org.jgrapht.core/org.jgrapht.interfaces=ALL-UNNAMED + + --add-exports org.jgrapht.core/org.jgrapht.perf.clique=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.connectivity=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.flow=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.graph=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.lca=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.matching=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.shortestpath=ALL-UNNAMED + --add-exports org.jgrapht.core/org.jgrapht.perf.spanning=ALL-UNNAMED + + + **/perf/** + + ${excludedGroups} + + **/FastTestSuite.java + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + --add-exports org.jgrapht.core/org.jgrapht.interfaces=ALL-UNNAMED + optional + slow + + **/*Test.java + + + + + diff --git a/jgrapht-core/src/main/java/module-info.java b/jgrapht-core/src/main/java/module-info.java new file mode 100644 index 00000000000..3fcfa013021 --- /dev/null +++ b/jgrapht-core/src/main/java/module-info.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Defines the core APIs of the JGraphT library. + * + * @since 1.5.0 + */ +module 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.steiner; + 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; + requires transitive org.apfloat; +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/DirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/DirectedGraph.java deleted file mode 100644 index aa4d5b5a147..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/DirectedGraph.java +++ /dev/null @@ -1,108 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * DirectedGraph.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * - */ -package org.jgrapht; - -import java.util.*; - - -/** - * A graph whose all edges are directed. This is the root interface of all - * directed graphs. - * - *

See - * http://mathworld.wolfram.com/DirectedGraph.html for more on directed - * graphs.

- * - * @author Barak Naveh - * @since Jul 14, 2003 - */ -public interface DirectedGraph - extends Graph -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the "in degree" of the specified vertex. An 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. - * - * @param vertex vertex whose degree is to be calculated. - * - * @return the degree of the specified vertex. - */ - public 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. - */ - public Set incomingEdgesOf(V vertex); - - /** - * Returns the "out degree" of the specified vertex. An 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. - * - * @param vertex vertex whose degree is to be calculated. - * - * @return the degree of the specified vertex. - */ - public 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. - */ - public Set outgoingEdgesOf(V vertex); -} - -// End DirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/EdgeFactory.java b/jgrapht-core/src/main/java/org/jgrapht/EdgeFactory.java deleted file mode 100644 index c9b82afc4c1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/EdgeFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * EdgeFactory.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * - */ -package org.jgrapht; - -/** - * An edge factory used by graphs for creating new edges. - * - * @author Barak Naveh - * @since Jul 14, 2003 - */ -public interface EdgeFactory -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a new edge whose endpoints are the specified source and target - * vertices. - * - * @param sourceVertex the source vertex. - * @param targetVertex the target vertex. - * - * @return a new edge whose endpoints are the specified source and target - * vertices. - */ - public E createEdge(V sourceVertex, V targetVertex); -} - -// End EdgeFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/Graph.java b/jgrapht-core/src/main/java/org/jgrapht/Graph.java index 18ab404a2f2..a2da7925a6d 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/Graph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/Graph.java @@ -1,435 +1,631 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ---------- - * Graph.java - * ---------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): John V. Sichi - * Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 06-Nov-2003 : Change edge sharing semantics (JVS); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 + * The root interface in the graph hierarchy. A mathematical graph-theory graph object + * {@code G(V,E)} contains a set {@code V} of vertices and a set {@code 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 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 {@code 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. + *

* - *

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 {@code V} and edges + * {@code E}. Such a graph can contain vertices of type {@code V} and all + * sub-types and Edges of type {@code E} and all sub-types. + *

* - *

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. * - *

For guidelines on vertex and edge classes, see this wiki - * page. + * @param the graph vertex type + * @param the graph edge type * * @author Barak Naveh - * @since Jul 14, 2003 */ public interface Graph { - //~ Methods ---------------------------------------------------------------- - /** - * 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. + * 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 {@code null}, returns + * {@code 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.

+ *

+ * 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. */ - public Set getAllEdges(V sourceVertex, V targetVertex); + 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 + * Returns an edge connecting source vertex to target vertex if such vertices and such edge + * exist in this graph. Otherwise returns {@code null}. + * If any of the specified vertices is {@code null} returns {@code null} * - *

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

+ *

+ * 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. */ - public E getEdge(V sourceVertex, V targetVertex); + E getEdge(V sourceVertex, V targetVertex); /** - * Returns the edge factory using which this graph creates new edges. The - * edge factory is defined when the graph is constructed and must not be - * modified. - * - * @return the edge factory using which this graph creates new edges. + * 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 {@code v} must not be equal + * to any other vertex in the graph. More formally, the graph must not contain any vertex + * {@code v2} such that {@code 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 {@code null} if the graph has no such supplier + * + * @throws UnsupportedOperationException if this graph disallows access to the vertex supplier */ - public EdgeFactory getEdgeFactory(); + Supplier getVertexSupplier(); /** - * 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, than 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 IllegalArgumentException is - * thrown.

- * - *

This method creates the new edge e using this graph's - * EdgeFactory. 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 returns - * null.

+ * 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 {@code 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 {@code e2} such that {@code e2.equals(e)}. + * + * @return the edge supplier {@code null} if the graph has no such supplier + * + * @throws UnsupportedOperationException if this graph disallows access to the edge 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 {@code 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 {@code e} using this graph's edge supplier (see + * {@link #getEdgeSupplier()}). For the new edge to be added {@code 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 {@code e2} such that + * {@code e2.equals(e)}. If such {@code e2} is found then the newly created edge + * {@code e} is abandoned, the method leaves this graph unchanged and returns {@code null}. + * + *

+ * If the underlying graph implementation's {@link #getEdgeSupplier()} returns + * {@code 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. + * @return The newly created edge if added to the graph, otherwise {@code 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 IllegalArgumentException if source or target vertices are not found in the graph + * or if there is a property of this graph that prevents the + * addition of the edge + * @throws NullPointerException if any of the specified vertices is {@code null}. + * @throws UnsupportedOperationException if the graph was not initialized with an edge supplier + * or if this graph disallows modification * - * @see #getEdgeFactory() + * @see #getEdgeSupplier() */ - public E addEdge(V sourceVertex, V targetVertex); + 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, than 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.

+ * Adds the specified edge to this graph, going from the source vertex to the target vertex. + * More formally, adds the specified edge, {@code e}, to this graph if this graph contains + * no edge {@code e2} such that {@code e2.equals(e)}. If this graph already contains such + * an edge, the call leaves this graph unchanged and returns {@code 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 {@code false}. If the edge was added to the graph, returns {@code true}. * * @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. + * @return {@code 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. + * @throws IllegalArgumentException if source or target vertices are not found in the graph + * or if there is a property of this graph that prevents the + * addition of the edge + * @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 arguments is {@code null} + * @throws UnsupportedOperationException if this graph disallows modification * * @see #addEdge(Object, Object) - * @see #getEdgeFactory() + * @see #getEdgeSupplier() */ - public boolean addEdge(V sourceVertex, V targetVertex, E e); + boolean addEdge(V sourceVertex, V targetVertex, E e); /** - * 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. + * Creates a new vertex in this graph and returns it. + * + *

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

+ * If the underlying graph implementation's {@link #getVertexSupplier()} returns + * {@code 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 this graph was not initialized with a vertex supplier + * or if this graph disallows modification + * + * @see #getVertexSupplier() + */ + V addVertex(); + + /** + * Adds the specified vertex to this graph if not already present. More formally, adds the + * specified vertex, {@code v}, to this graph if this graph contains no vertex + * {@code u} such that {@code u.equals(v)}. + * If this graph already contains such vertex, the call leaves this graph + * unchanged and returns {@code 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. + * @return {@code true} if this graph did not already contain the specified vertex. * - * @throws NullPointerException if the specified vertex is - * null. + * @throws IllegalArgumentException if there is a property that disallows adding the specified vertex + * @throws NullPointerException if the specified vertex is {@code null}. + * @throws UnsupportedOperationException if this graph disallows modification */ - public boolean addVertex(V v); + 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. + * Returns {@code 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 + * {@code null}, returns {@code false}. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * - * @return true if this graph contains the specified edge. + * @return {@code true} if this graph contains the specified edge. */ - public boolean containsEdge(V sourceVertex, V targetVertex); + 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. + * Returns {@code true} if this graph contains the specified edge. More formally, returns + * {@code true} if and only if this graph contains an edge {@code e2} such that + * {@code e.equals(e2)}. If the specified edge is {@code null} returns + * {@code false}. * * @param e edge whose presence in this graph is to be tested. * - * @return true if this graph contains the specified edge. + * @return {@code true} if this graph contains the specified edge. */ - public boolean containsEdge(E e); + 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. + * Returns {@code true} if this graph contains the specified vertex. More formally, returns + * {@code true} if and only if this graph contains a vertex {@code u} such that + * {@code u.equals(v)}. If the specified vertex is {@code null} returns + * {@code false}. * * @param v vertex whose presence in this graph is to be tested. * - * @return true if this graph contains the specified vertex. + * @return {@code true} if this graph contains the specified vertex. */ - public boolean containsVertex(V v); + 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. + * 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.

+ *

+ * 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. */ - public Set edgeSet(); + Set edgeSet(); /** - * Returns a set of all edges touching the specified vertex. If no edges are - * touching the specified vertex returns an empty set. + * 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. * - * @param vertex the vertex for which a set of touching edges is to be - * returned. + * @throws IllegalArgumentException if vertex is not found in the graph. + * @throws NullPointerException if vertex is {@code 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. + * @throws NullPointerException if vertex is {@code 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 {@code 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 {@code null}. */ - public Set edgesOf(V vertex); + Set incomingEdgesOf(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. + * 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 {@code 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 {@code 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 + * @return {@code true} if this graph changed as a result of the call * - * @throws NullPointerException if the specified edge collection is - * null. + * @throws NullPointerException if the specified edge collection is {@code null} + * @throws UnsupportedOperationException if this graph disallows modification * * @see #removeEdge(Object) * @see #containsEdge(Object) */ - public boolean removeAllEdges(Collection edges); + 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. + * Removes all the edges going from the specified source vertex to the specified target vertex, + * and returns a set of all removed edges. Returns {@code 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 no either vertex not - * part of graph + * @return the removed edges, or {@code null} if either vertex is not part of graph + * + * @throws UnsupportedOperationException if this graph disallows modification */ - public Set removeAllEdges(V sourceVertex, V targetVertex); + 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. + * 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 + * @return {@code true} if this graph changed as a result of the call * - * @throws NullPointerException if the specified vertex collection is - * null. + * @throws NullPointerException if the specified vertex collection is {@code null}. + * @throws UnsupportedOperationException if this graph disallows modification * * @see #removeVertex(Object) * @see #containsVertex(Object) */ - public boolean removeAllVertices(Collection vertices); + 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. + * 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 {@code 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. + * @return The removed edge, or {@code null} if no edge removed. + * + * @throws UnsupportedOperationException if this graph disallows modification */ - public E removeEdge(V sourceVertex, V targetVertex); + 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). + * Removes the specified edge from the graph. Removes the specified edge from this graph if it + * is present. More formally, removes an edge {@code e2} such that {@code e2.equals(e)}, + * if the graph contains such edge. Returns {@code 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.

+ *

+ * If the specified edge is {@code null} returns {@code false}. + *

* * @param e edge to be removed from this graph, if present. * - * @return true if and only if the graph contained the - * specified edge. + * @return {@code true} if and only if the graph contained the specified edge. + * + * @throws UnsupportedOperationException if this graph disallows modification */ - public boolean removeEdge(E e); + 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). + * Removes the specified vertex from this graph including all its touching edges if present. + * More formally, if the graph contains a vertex {@code u} such that {@code u.equals(v)}, + * the call removes all edges that touch {@code u} and then removes {@code u} itself. + * If no such {@code u} is found, the call leaves the graph unchanged. + * Returns {@code 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.

+ *

+ * If the specified vertex is {@code null} returns {@code false}. + *

* * @param v vertex to be removed from this graph, if present. * - * @return true if the graph contained the specified vertex; - * false otherwise. + * @return {@code true} if the graph contained the specified vertex; {@code false} + * otherwise. + * + * @throws UnsupportedOperationException if this graph disallows modification */ - public boolean removeVertex(V v); + 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. + * 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.

+ *

+ * 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. */ - public Set vertexSet(); + Set vertexSet(); /** - * Returns the source vertex of an edge. For an undirected graph, source and - * target are distinguishable designations (but without any mathematical - * meaning). + * 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 */ - public V getEdgeSource(E e); + 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). + * 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 */ - public V getEdgeTarget(E e); + 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(); /** - * Returns the weight assigned to a given edge. Unweighted graphs return 1.0 - * (as defined by {@link WeightedGraph#DEFAULT_EDGE_WEIGHT}), allowing - * weighted-graph algorithms to apply to them where meaningful. + * 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 - * - * @see WeightedGraph + * + * @throws IllegalArgumentException if the specified edge is not in this graph + * @throws NullPointerException if argument is {@code null} */ - public double getEdgeWeight(E e); -} + 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 NullPointerException if {@code e} is {@code null} + * @throws UnsupportedOperationException if the graph does not support weights + * or if there is a property of this graph that + * disallows modification of the weight + */ + void setEdgeWeight(E e, double weight); + + /** + * Assigns a weight to an edge between {@code sourceVertex} and {@code targetVertex}. + *

+ * When there exist multiple edges between {@code sourceVertex} and + * {@code 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 NullPointerException if either one of {@code sourceVertex} or + * {@code targetVertex} is {@code null}, or + * if there is no edge between the two vertices + * @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); + } -// End Graph.java + /** + * 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphHelper.java b/jgrapht-core/src/main/java/org/jgrapht/GraphHelper.java deleted file mode 100644 index e45510a5f1e..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/GraphHelper.java +++ /dev/null @@ -1,59 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * GraphHelper.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * Mikael Hansen - * - * $Id$ - * - * Changes - * ------- - * 10-Jul-2003 : Initial revision (BN); - * 06-Nov-2003 : Change edge sharing semantics (JVS); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht; - -/** - * A collection of utilities to assist the working with graphs. - * - * @author Barak Naveh - * @since Jul 31, 2003 - * @deprecated Use {@link Graphs} instead. - */ -@Deprecated public abstract class GraphHelper - extends Graphs -{ -} - -// End GraphHelper.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphIterables.java b/jgrapht-core/src/main/java/org/jgrapht/GraphIterables.java new file mode 100644 index 00000000000..d012fedb429 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphIterables.java @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code null}, returns {@code 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 {@code null}. + */ + default Iterable allEdges(V sourceVertex, V targetVertex) + { + return new LiveIterableWrapper<>(() -> getGraph().getAllEdges(sourceVertex, targetVertex)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphMapping.java b/jgrapht-core/src/main/java/org/jgrapht/GraphMapping.java index 1f70b58e015..e9045f343cc 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/GraphMapping.java +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphMapping.java @@ -1,75 +1,54 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. + * JGraphT : a free Java graph-theory library * - * 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. + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * 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. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * 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. - */ -/* ----------------- - * GraphMapping.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): John V. Sichi - * - * Changes - * ------- + * 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). + * 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 - * @since Jul 30, 2005 */ public interface GraphMapping { - //~ Methods ---------------------------------------------------------------- - /** - * Gets the mapped value where the key is vertex + * Gets the mapped value where the key is {@code 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 + * @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 */ - public V getVertexCorrespondence(V vertex, boolean forward); + V getVertexCorrespondence(V vertex, boolean forward); /** - * Gets the mapped value where the key is edge + * Gets the mapped value where the key is {@code 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 + * @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 */ - public E getEdgeCorrespondence(E edge, boolean forward); + E getEdgeCorrespondence(E edge, boolean forward); } - -// End GraphMapping.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphMetrics.java b/jgrapht-core/src/main/java/org/jgrapht/GraphMetrics.java new file mode 100644 index 00000000000..c128a57000d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphMetrics.java @@ -0,0 +1,362 @@ +/* + * (C) Copyright 2017-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphPath.java b/jgrapht-core/src/main/java/org/jgrapht/GraphPath.java index c08e679a93d..e333ffb4385 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/GraphPath.java +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphPath.java @@ -1,105 +1,133 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------- - * Graph.java - * ---------- - * (C) Copyright 2008-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2008-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 1-Jan-2008 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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}. Note that a path is defined primarily in terms - * of edges (rather than vertices) so that multiple edges between the same pair - * of vertices can be discriminated. + * 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 - * @since Jan 1, 2008 */ public interface GraphPath { - //~ Methods ---------------------------------------------------------------- - /** - * Returns the graph over which this path is defined. The path may also be - * valid with respect to other graphs. + * Returns the graph over which this path is defined. The path may also be valid with respect to + * other graphs. * * @return the containing graph */ - public Graph getGraph(); + Graph getGraph(); /** * Returns the start vertex in the path. * * @return the start vertex */ - public V getStartVertex(); + V getStartVertex(); /** * Returns the end vertex in the path. * * @return the end vertex */ - public V getEndVertex(); + 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; {@link - * Graphs#getPathVertexList} provides a convenience method for this. + * 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. + *

+ * Whether or not the returned edge list is modifiable depends on the path implementation. * * @return list of edges traversed by the path */ - public List getEdgeList(); + 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 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. + * 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 */ - public double getWeight(); -} + double getWeight(); -// End GraphPath.java + /** + * 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(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java b/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java new file mode 100644 index 00000000000..929cba86c95 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java @@ -0,0 +1,868 @@ +/* + * (C) Copyright 2003-2023, 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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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 + * + * @throws NullPointerException if graph is {@code null} + */ + 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 + * + * @throws NullPointerException if graph is {@code null} + * + * @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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphType.java b/jgrapht-core/src/main/java/org/jgrapht/GraphType.java new file mode 100644 index 00000000000..dcd344acc96 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphType.java @@ -0,0 +1,166 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 {@code 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 {@code true} if all edges of the graph are undirected, false otherwise. + * + * @return {@code true} if all edges of the graph are undirected, false otherwise + */ + boolean isUndirected(); + + /** + * Returns {@code true} if the graph contain both directed and undirected edges, false otherwise. + * + * @return {@code true} if the graph contain both directed and undirected edges, false otherwise + */ + boolean isMixed(); + + /** + * Returns {@code 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 {@code true} if and only if multiple (parallel) edges are allowed. + */ + boolean isAllowingMultipleEdges(); + + /** + * Returns {@code 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 {@code true} if and only if graph self-loops are allowed. + */ + boolean isAllowingSelfLoops(); + + /** + * Returns {@code true} if and only if cycles are allowed in this graph. + * + * @return {@code true} if and only if graph cycles are allowed. + */ + boolean isAllowingCycles(); + + /** + * Returns {@code true} if and only if the graph supports edge weights. + * + * @return {@code true} if the graph supports edge weights, {@code false} otherwise. + */ + boolean isWeighted(); + + /** + * Returns {@code true} if the graph is simple, {@code false} otherwise. + * + * @return {@code true} if the graph is simple, {@code false} otherwise + */ + boolean isSimple(); + + /** + * Returns {@code true} if the graph is a pseudograph, {@code false} otherwise. + * + * @return {@code true} if the graph is a pseudograph, {@code false} otherwise + */ + boolean isPseudograph(); + + /** + * Returns {@code true} if the graph is a multigraph, {@code false} otherwise. + * + * @return {@code true} if the graph is a multigraph, {@code false} otherwise + */ + boolean isMultigraph(); + + /** + * Returns {@code true} if the graph is modifiable, {@code false} otherwise. + * + * @return {@code true} if the graph is modifiable, {@code 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(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/Graphs.java b/jgrapht-core/src/main/java/org/jgrapht/Graphs.java index 0b2754bf6c9..70894b35de7 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/Graphs.java +++ b/jgrapht-core/src/main/java/org/jgrapht/Graphs.java @@ -1,112 +1,86 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * Graphs.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * Mikael Hansen + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Jul-2003 : Initial revision (BN); - * 06-Nov-2003 : Change edge sharing semantics (JVS); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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 - * @since Jul 31, 2003 */ public abstract class Graphs { - //~ Methods ---------------------------------------------------------------- /** * 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 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. + * @return The newly created edge if added to the graph, otherwise {@code null}. + * + * @throws NullPointerException if any one of {@code g}, {@code sourceVertex}, or {@code targetVertex} is {@code 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) + public static E addEdge(Graph g, V sourceVertex, V targetVertex, double weight) { - EdgeFactory ef = g.getEdgeFactory(); - E e = ef.createEdge(sourceVertex, targetVertex); - - // we first create the edge and set the weight to make sure that - // listeners will see the correct weight upon addEdge. - - assert (g instanceof WeightedGraph) : g.getClass(); - ((WeightedGraph) g).setEdgeWeight(e, weight); + Supplier edgeSupplier = g.getEdgeSupplier(); + if (edgeSupplier == null) { + throw new UnsupportedOperationException("Graph contains no edge supplier"); + } + E e = edgeSupplier.get(); - return g.addEdge(sourceVertex, targetVertex, e) ? e : null; + 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. + * 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 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. + * @return The newly created edge if added to the graph, otherwise {@code null}. + * + * @throws NullPointerException if any one of the arguments is {@code null} */ - public static E addEdgeWithVertices( - Graph g, - V sourceVertex, - V targetVertex) + public static E addEdgeWithVertices(Graph g, V sourceVertex, V targetVertex) { g.addVertex(sourceVertex); g.addVertex(targetVertex); @@ -115,21 +89,18 @@ public static E addEdgeWithVertices( } /** - * Adds the specified edge to the graph, including its vertices if not - * already included. + * 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 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. + * @return {@code true} if the target graph did not already contain the specified edge. */ - public static boolean addEdgeWithVertices( - Graph targetGraph, - Graph sourceGraph, - E edge) + public static boolean addEdgeWithVertices(Graph targetGraph, Graph sourceGraph, E edge) { V sourceVertex = sourceGraph.getEdgeSource(edge); V targetVertex = sourceGraph.getEdgeTarget(edge); @@ -141,24 +112,23 @@ public static boolean addEdgeWithVertices( } /** - * 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. - * - * @return The newly created edge if added to the graph, otherwise - * null. + * 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 {@code null}. + * + * @throws NullPointerException if any one of {@code g}, {@code sourceVertex}, or {@code targetVertex} is {@code null} */ - public static E addEdgeWithVertices( - Graph g, - V sourceVertex, - V targetVertex, - double weight) + public static E addEdgeWithVertices(Graph g, V sourceVertex, V targetVertex, double weight) { g.addVertex(sourceVertex); g.addVertex(targetVertex); @@ -167,25 +137,27 @@ public static E addEdgeWithVertices( } /** - * 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. - * - * @return true if and only if the destination graph has been - * changed as a result of this operation. + * 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 + * {@code true} if the destination graph has been modified as a result of this operation, + * otherwise it returns {@code 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 {@code 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) + public static boolean addGraph(Graph destination, Graph source) { boolean modified = addAllVertices(destination, source.vertexSet()); modified |= addAllEdges(destination, source, source.edgeSet()); @@ -194,49 +166,52 @@ public static boolean addGraph( } /** - * 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. + * 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.

+ *

+ * 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 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( - DirectedGraph destination, - DirectedGraph source) + 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)); + 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. + * 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 + * @return {@code true} if this graph changed as a result of the call */ public static boolean addAllEdges( - Graph destination, - Graph source, - Collection edges) + Graph destination, Graph source, Collection edges) { boolean modified = false; @@ -252,25 +227,24 @@ public static boolean addAllEdges( } /** - * 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. + * 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 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 + * @return {@code 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. + * @throws NullPointerException if the specified vertices contains one or more {@code null} vertices, or + * if the specified vertex collection is {@code null}. * * @see Graph#addVertex(Object) */ public static boolean addAllVertices( - Graph destination, - Collection vertices) + Graph destination, Collection vertices) { boolean modified = false; @@ -282,22 +256,24 @@ public static boolean addAllVertices( } /** - * 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. + * 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 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. + * @return a list of the vertices that are the neighbors of the specified vertex. */ - public static List neighborListOf(Graph g, - V vertex) + public static List neighborListOf(Graph g, V vertex) { - List neighbors = new ArrayList(); + List neighbors = new ArrayList<>(); - for (E e : g.edgesOf(vertex)) { + for (E e : g.iterables().edgesOf(vertex)) { neighbors.add(getOppositeVertex(g, e, vertex)); } @@ -305,24 +281,44 @@ public static List neighborListOf(Graph g, } /** - * 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. + * Returns a set of vertices that are neighbors of a specified vertex. * - * @param g the graph to look for predecessors in. - * @param vertex the vertex to get the predecessors of. + * @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. * - * @return a list of the vertices that are the direct predecessors of the - * specified vertex. + * @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( - DirectedGraph g, - V vertex) + public static List predecessorListOf(Graph g, V vertex) { - List predecessors = new ArrayList(); - Set edges = g.incomingEdgesOf(vertex); + List predecessors = new ArrayList<>(); - for (E e : edges) { + for (E e : g.iterables().incomingEdgesOf(vertex)) { predecessors.add(getOppositeVertex(g, e, vertex)); } @@ -330,24 +326,24 @@ public static List predecessorListOf( } /** - * 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. + * 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 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. + * @return a list of the vertices that are the direct successors of the specified vertex. */ - public static List successorListOf( - DirectedGraph g, - V vertex) + public static List successorListOf(Graph g, V vertex) { - List successors = new ArrayList(); - Set edges = g.outgoingEdgesOf(vertex); + List successors = new ArrayList<>(); - for (E e : edges) { + for (E e : g.iterables().outgoingEdgesOf(vertex)) { successors.add(getOppositeVertex(g, e, vertex)); } @@ -355,29 +351,28 @@ public static List successorListOf( } /** - * 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. + * 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. * - * @return an undirected view of the specified graph, if it is directed, or - * or the specified graph itself if it is already undirected. + * @param g the graph for which an undirected view is to be returned + * @param the graph vertex type + * @param the graph edge type * - * @throws IllegalArgumentException if the graph is neither DirectedGraph - * nor UndirectedGraph. + * @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 UndirectedGraph undirectedGraph(Graph g) + public static Graph undirectedGraph(Graph g) { - if (g instanceof DirectedGraph) { - return new AsUndirectedGraph((DirectedGraph) g); - } else if (g instanceof UndirectedGraph) { - return (UndirectedGraph) g; + if (g.getType().isDirected()) { + return new AsUndirectedGraph<>(g); + } else if (g.getType().isUndirected()) { + return g; } else { - throw new IllegalArgumentException( - "Graph must be either DirectedGraph or UndirectedGraph"); + throw new IllegalArgumentException("graph must be either directed or undirected"); } } @@ -387,13 +382,14 @@ public static UndirectedGraph undirectedGraph(Graph g) * @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)); + return (g.getEdgeSource(e).equals(v)) || (g.getEdgeTarget(e).equals(v)); } /** @@ -402,6 +398,8 @@ public static boolean testIncidence(Graph g, E e, V v) * @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 */ @@ -414,29 +412,185 @@ public static V getOppositeVertex(Graph g, E e, V v) } else if (v.equals(target)) { return source; } else { - throw new IllegalArgumentException("no such vertex"); + throw new IllegalArgumentException("no such vertex: " + v.toString()); } } /** - * Gets the list of vertices visited by a path. + * 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 path path of interest + * @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 corresponding vertex list + * @return true if the graph contained the specified vertex; false otherwise. */ - public static List getPathVertexList(GraphPath path) + public static boolean removeVertexAndPreserveConnectivity(Graph graph, V vertex) { - Graph g = path.getGraph(); - List list = new ArrayList(); - V v = path.getStartVertex(); - list.add(v); - for (E e : path.getEdgeList()) { - v = getOppositeVertex(g, e, v); - list.add(v); + 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); + } } - return list; + + graph.removeVertex(vertex); + return true; } -} -// End Graphs.java + /** + * 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()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/ListenableGraph.java b/jgrapht-core/src/main/java/org/jgrapht/ListenableGraph.java index 7cba50d0fba..9658d9a41ff 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/ListenableGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/ListenableGraph.java @@ -1,62 +1,38 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------- - * ListenableGraph.java - * -------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. * - * @author Barak Naveh + * @param the graph vertex type + * @param the graph edge type + * * @see GraphListener * @see VertexSetListener - * @since Jul 20, 2003 + * + * @author Barak Naveh */ public interface ListenableGraph extends Graph { - //~ Methods ---------------------------------------------------------------- - /** * Adds the specified graph listener to this graph, if not already present. * @@ -65,8 +41,7 @@ public interface ListenableGraph public void addGraphListener(GraphListener l); /** - * Adds the specified vertex set listener to this graph, if not already - * present. + * Adds the specified vertex set listener to this graph, if not already present. * * @param l the listener to be added. */ @@ -86,5 +61,3 @@ public interface ListenableGraph */ public void removeVertexSetListener(VertexSetListener l); } - -// End ListenableGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/UndirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/UndirectedGraph.java deleted file mode 100644 index 6429f16fc04..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/UndirectedGraph.java +++ /dev/null @@ -1,70 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------- - * UndirectedGraph.java - * -------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * - */ -package org.jgrapht; - -/** - * A graph whose all edges are undirected. This is the root interface of all - * undirected graphs. - * - *

See - * http://mathworld.wolfram.com/Graph.html for more on undirected and on - * directed graphs.

- * - * @author Barak Naveh - * @since Jul 14, 2003 - */ -public interface UndirectedGraph - extends Graph -{ - //~ Methods ---------------------------------------------------------------- - - /** - * 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. - */ - public int degreeOf(V vertex); -} - -// End UndirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/VertexFactory.java b/jgrapht-core/src/main/java/org/jgrapht/VertexFactory.java deleted file mode 100644 index 86e8551acdf..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/VertexFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * VertexFactory.java - * ------------------ - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); - * 11-Mar-2004 : Made generic (CH); - * - */ -package org.jgrapht; - -/** - * A vertex factory used by graph algorithms for creating new vertices. - * Normally, vertices are constructed by user code and added to a graph - * explicitly, but algorithms which generate new vertices require a factory. - * - * @author John V. Sichi - * @since Sep 16, 2003 - */ -public interface VertexFactory -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a new vertex. - * - * @return the new vertex - */ - public V createVertex(); -} - -// End VertexFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/WeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/WeightedGraph.java deleted file mode 100644 index 0d29745b716..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/WeightedGraph.java +++ /dev/null @@ -1,71 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * WeightedGraph.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 13-Aug-2003 : Included weight methods in Edge interface (BN); - * 11-Mar-2004 : Made generic (CH); - * - */ -package org.jgrapht; - -/** - * An interface for a graph whose edges have non-uniform weights. - * - * @author Barak Naveh - * @since Jul 23, 2003 - */ -public interface WeightedGraph - extends Graph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - * The default weight for an edge. - */ - public static double DEFAULT_EDGE_WEIGHT = 1.0; - - //~ Methods ---------------------------------------------------------------- - - /** - * Assigns a weight to an edge. - * - * @param e edge on which to set weight - * @param weight new weight for edge - */ - public void setEdgeWeight(E e, double weight); -} - -// End WeightedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElement.java b/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElement.java deleted file mode 100644 index 5999888f219..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElement.java +++ /dev/null @@ -1,202 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * AbstractPathElement.java - * ------------------------- - * (C) Copyright 2006-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jan-2006 : Initial revision (GB); - * 14-Jan-2006 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A new path is created from a path concatenated to an edge. It's like a linked - * list.
- * The empty path is composed only of one vertex.
- * In this case the path has no previous path element.
- * . - * - *

NOTE jvs 1-Jan-2008: This is an internal data structure for use in - * algorithms. For returning paths to callers, use the public {@link GraphPath} - * interface instead. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -abstract class AbstractPathElement -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Number of hops of the path. - */ - protected int nHops; - - /** - * Edge reaching the target vertex of the path. - */ - protected E prevEdge; - - /** - * Previous path element. - */ - protected AbstractPathElement prevPathElement; - - /** - * Target vertex. - */ - private V vertex; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a path element by concatenation of an edge to a path element. - * - * @param pathElement - * @param edge edge reaching the end vertex of the path element created. - */ - protected AbstractPathElement( - Graph graph, - AbstractPathElement pathElement, - E edge) - { - this.vertex = - Graphs.getOppositeVertex( - graph, - edge, - pathElement.getVertex()); - this.prevEdge = edge; - this.prevPathElement = pathElement; - - this.nHops = pathElement.getHopCount() + 1; - } - - /** - * Copy constructor. - * - * @param original source to copy from - */ - protected AbstractPathElement(AbstractPathElement original) - { - this.nHops = original.nHops; - this.prevEdge = original.prevEdge; - this.prevPathElement = original.prevPathElement; - this.vertex = original.vertex; - } - - /** - * Creates an empty path element. - * - * @param vertex end vertex of the path element. - */ - protected AbstractPathElement(V vertex) - { - this.vertex = vertex; - this.prevEdge = null; - this.prevPathElement = null; - - this.nHops = 0; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the path as a list of edges. - * - * @return list of Edge. - */ - public List createEdgeListPath() - { - List path = new ArrayList(); - AbstractPathElement pathElement = this; - - // while start vertex is not reached. - while (pathElement.getPrevEdge() != null) { - path.add(pathElement.getPrevEdge()); - - pathElement = pathElement.getPrevPathElement(); - } - - Collections.reverse(path); - - return path; - } - - /** - * Returns the number of hops (or number of edges) of the path. - * - * @return . - */ - public int getHopCount() - { - return this.nHops; - } - - /** - * Returns the edge reaching the target vertex of the path. - * - * @return null if the path is empty. - */ - public E getPrevEdge() - { - return this.prevEdge; - } - - /** - * Returns the previous path element. - * - * @return null is the path is empty. - */ - public AbstractPathElement getPrevPathElement() - { - return this.prevPathElement; - } - - /** - * Returns the target vertex of the path. - * - * @return . - */ - public V getVertex() - { - return this.vertex; - } -} - -// End AbstractPathElement.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElementList.java b/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElementList.java deleted file mode 100644 index a81082b4244..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/AbstractPathElementList.java +++ /dev/null @@ -1,196 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * AbstractPathElementList.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * List of paths AbstractPathElement with same target vertex. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -abstract class AbstractPathElementList> - extends AbstractList -{ - //~ Instance fields -------------------------------------------------------- - - protected Graph graph; - - /** - * Max number of stored paths. - */ - protected int maxSize; - - /** - * Stored paths, list of AbstractPathElement. - */ - protected ArrayList pathElements = new ArrayList(); - - /** - * Target vertex of the paths. - */ - protected V vertex; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates paths obtained by concatenating the specified edge to the - * specified paths. - * - * @param maxSize maximum number of paths the list is able to store. - * @param elementList paths, list of AbstractPathElement. - * @param edge edge reaching the end vertex of the created paths. - * - * @throws NullPointerException if the specified prevPathElementList or edge - * is null. - * @throws IllegalArgumentException if maxSize is negative or - * 0. - */ - protected AbstractPathElementList( - Graph graph, - int maxSize, - AbstractPathElementList elementList, - E edge) - { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize is negative or 0"); - } - if (elementList == null) { - throw new NullPointerException("elementList is null"); - } - if (edge == null) { - throw new NullPointerException("edge is null"); - } - - this.graph = graph; - this.maxSize = maxSize; - this.vertex = - Graphs.getOppositeVertex(graph, edge, elementList.getVertex()); - } - - /** - * Creates a list with an empty path. The list size is 1. - * - * @param maxSize maximum number of paths the list is able to store. - * - * @throws NullPointerException if the specified path-element is - * null. - * @throws IllegalArgumentException if maxSize is negative or - * 0. - * @throws IllegalArgumentException if pathElement is not - * empty. - */ - protected AbstractPathElementList( - Graph graph, - int maxSize, - T pathElement) - { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize is negative or 0"); - } - if (pathElement == null) { - throw new NullPointerException("pathElement is null"); - } - if (pathElement.getPrevEdge() != null) { - throw new IllegalArgumentException("path must be empty"); - } - - this.graph = graph; - this.maxSize = maxSize; - this.vertex = pathElement.getVertex(); - - this.pathElements.add(pathElement); - } - - /** - * Creates an empty list. The list size is 0. - * - * @param maxSize maximum number of paths the list is able to store. - * - * @throws IllegalArgumentException if maxSize is negative or - * 0. - */ - protected AbstractPathElementList(Graph graph, int maxSize, V vertex) - { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize is negative or 0"); - } - - this.graph = graph; - this.maxSize = maxSize; - this.vertex = vertex; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns path AbstractPathElement stored at the specified - * index. - */ - public T get(int index) - { - return this.pathElements.get(index); - } - - /** - * Returns target vertex. - */ - public V getVertex() - { - return this.vertex; - } - - /** - * Returns the number of paths stored in the list. - */ - public int size() - { - return this.pathElements.size(); - } -} - -// End AbstractPathElementList.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordIterator.java deleted file mode 100644 index 680add25a96..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordIterator.java +++ /dev/null @@ -1,448 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BellmanFordIterator.java - * ------------------------- - * (C) Copyright 2006-2008, by France Telecom and Contributors. - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jan-2006 : Initial revision (GB); - * 14-Jan-2006 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Helper class for {@link BellmanFordShortestPath}; not intended for general - * use. - */ -class BellmanFordIterator - implements Iterator> -{ - //~ Static fields/initializers --------------------------------------------- - - /** - * Error message. - */ - protected final static String NEGATIVE_UNDIRECTED_EDGE = - "Negative" - + "edge-weights are not allowed in an unidrected graph!"; - - //~ Instance fields -------------------------------------------------------- - - /** - * Graph on which shortest paths are searched. - */ - protected Graph graph; - - /** - * Start vertex. - */ - protected V startVertex; - - /** - * Vertices whose shortest path cost have been improved during the previous - * pass. - */ - private List prevImprovedVertices = new ArrayList(); - - private Map> prevVertexData; - - private boolean startVertexEncountered = false; - - /** - * Stores the vertices that have been seen during iteration and (optionally) - * some additional traversal info regarding each vertex. - */ - private Map> vertexData; - - private double epsilon; - - //~ Constructors ----------------------------------------------------------- - - /** - * @param graph - * @param startVertex start vertex. - * @param epsilon tolerance factor. - */ - protected BellmanFordIterator( - Graph graph, - V startVertex, - double epsilon) - { - assertBellmanFordIterator(graph, startVertex); - - this.graph = graph; - this.startVertex = startVertex; - this.epsilon = epsilon; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the path element of the shortest path with less than - * nMaxHops edges between the start vertex and the end vertex. - * - * @param endVertex end vertex. - * - * @return . - */ - public BellmanFordPathElement getPathElement(V endVertex) - { - return getSeenData(endVertex); - } - - /** - * @return true if at least one path has been improved during - * the previous pass, false otherwise. - */ - public boolean hasNext() - { - if (!this.startVertexEncountered) { - encounterStartVertex(); - } - - return !(this.prevImprovedVertices.isEmpty()); - } - - /** - * Returns the list Collection of vertices whose path has been - * improved during the current pass. - * - * @see java.util.Iterator#next() - */ - public List next() - { - if (!this.startVertexEncountered) { - encounterStartVertex(); - } - - if (hasNext()) { - List improvedVertices = new ArrayList(); - for (int i = this.prevImprovedVertices.size() - 1; i >= 0; i--) { - V vertex = this.prevImprovedVertices.get(i); - for ( - Iterator iter = edgesOfIterator(vertex); - iter.hasNext();) - { - E edge = iter.next(); - V oppositeVertex = - Graphs.getOppositeVertex( - graph, - edge, - vertex); - if (getPathElement(oppositeVertex) != null) { - boolean relaxed = - relaxVertexAgain(oppositeVertex, edge); - if (relaxed) { - improvedVertices.add(oppositeVertex); - } - } else { - relaxVertex(oppositeVertex, edge); - improvedVertices.add(oppositeVertex); - } - } - } - - savePassData(improvedVertices); - - return improvedVertices; - } - - throw new NoSuchElementException(); - } - - /** - * Unsupported - * - * @see java.util.Iterator#remove() - */ - public void remove() - { - throw new UnsupportedOperationException(); - } - - /** - * @param edge - * - * @throws IllegalArgumentException if the graph is undirected and the - * edge-weight is negative. - */ - protected void assertValidEdge(E edge) - { - if (this.graph instanceof UndirectedGraph) { - if (graph.getEdgeWeight(edge) < 0) { - throw new IllegalArgumentException(NEGATIVE_UNDIRECTED_EDGE); - } - } - } - - /** - * Costs taken into account are the weights stored in Edge - * objects. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - * - * @return the cost obtained by concatenation. - * - * @see Graph#getEdgeWeight(E) - */ - protected double calculatePathCost(V vertex, E edge) - { - V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); - - // we get the data of the previous pass. - BellmanFordPathElement oppositePrevData = - getPrevSeenData(oppositeVertex); - - double pathCost = graph.getEdgeWeight(edge); - - if (!oppositePrevData.getVertex().equals(this.startVertex)) { - // if it's not the start vertex, we add the cost of the previous - // pass. - pathCost += oppositePrevData.getCost(); - } - - return pathCost; - } - - /** - * Returns an iterator to loop over outgoing edges Edge of the - * vertex. - * - * @param vertex - * - * @return . - */ - protected Iterator edgesOfIterator(V vertex) - { - if (this.graph instanceof DirectedGraph) { - return ((DirectedGraph) this.graph).outgoingEdgesOf(vertex) - .iterator(); - } else { - return this.graph.edgesOf(vertex).iterator(); - } - } - - /** - * Access the data stored for a seen vertex in the previous pass. - * - * @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. - */ - protected BellmanFordPathElement getPrevSeenData(V vertex) - { - return this.prevVertexData.get(vertex); - } - - /** - * Access the data stored for a seen vertex in the current pass. - * - * @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. - */ - protected BellmanFordPathElement getSeenData(V vertex) - { - return this.vertexData.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 this.vertexData.containsKey(vertex); - } - - /** - * @param vertex - * @param data - * - * @return . - */ - protected BellmanFordPathElement putPrevSeenData( - V vertex, - BellmanFordPathElement data) - { - if (this.prevVertexData == null) { - this.prevVertexData = - new HashMap>(); - } - - return this.prevVertexData.put(vertex, data); - } - - /** - * Stores iterator-dependent data for a vertex that has been seen during the - * current pass. - * - * @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. - */ - protected BellmanFordPathElement putSeenData( - V vertex, - BellmanFordPathElement data) - { - if (this.vertexData == null) { - this.vertexData = new HashMap>(); - } - - return this.vertexData.put(vertex, data); - } - - private void assertBellmanFordIterator(Graph graph, V startVertex) - { - if (!(graph.containsVertex(startVertex))) { - throw new IllegalArgumentException( - "Graph must contain the start vertex!"); - } - } - - /** - * The first time we see a vertex, make up a new entry for it. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - * @param cost cost of the created path element. - * - * @return the new entry. - */ - private BellmanFordPathElement createSeenData( - V vertex, - E edge, - double cost) - { - BellmanFordPathElement prevPathElement = - getPrevSeenData( - Graphs.getOppositeVertex(graph, edge, vertex)); - - BellmanFordPathElement data = - new BellmanFordPathElement( - graph, - prevPathElement, - edge, - cost, - epsilon); - - return data; - } - - private void encounterStartVertex() - { - BellmanFordPathElement data = - new BellmanFordPathElement( - this.startVertex, - epsilon); - - // first the only vertex considered as improved is the start vertex. - this.prevImprovedVertices.add(this.startVertex); - - putSeenData(this.startVertex, data); - putPrevSeenData(this.startVertex, data); - - this.startVertexEncountered = true; - } - - /** - * Upates data first time a vertex is reached by a path. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - */ - private void relaxVertex(V vertex, E edge) - { - assertValidEdge(edge); - - double shortestPathCost = calculatePathCost(vertex, edge); - - BellmanFordPathElement data = - createSeenData(vertex, edge, - shortestPathCost); - - putSeenData(vertex, data); - } - - /** - * Check if the cost of the best path so far reaching the specified vertex - * could be improved if the vertex is reached through the specified edge. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - * - * @return true if the cost has been improved, - * false otherwise. - */ - private boolean relaxVertexAgain(V vertex, E edge) - { - assertValidEdge(edge); - - double candidateCost = calculatePathCost(vertex, edge); - - // we get the data of the previous pass. - BellmanFordPathElement oppositePrevData = - getPrevSeenData( - Graphs.getOppositeVertex(graph, edge, vertex)); - - BellmanFordPathElement pathElement = getSeenData(vertex); - return pathElement.improve(oppositePrevData, edge, candidateCost); - } - - private void savePassData(List improvedVertices) - { - for (V vertex : improvedVertices) { - BellmanFordPathElement orig = getSeenData(vertex); - BellmanFordPathElement clonedData = - new BellmanFordPathElement(orig); - putPrevSeenData(vertex, clonedData); - } - - this.prevImprovedVertices = improvedVertices; - } -} - -// End BellmanFordIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordPathElement.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordPathElement.java deleted file mode 100644 index 8b88264b7a1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordPathElement.java +++ /dev/null @@ -1,150 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BellmanFordPathElement.java - * ------------------------- - * (C) Copyright 2006-2008, by France Telecom and Contributors. - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jan-2006 : Initial revision (GB); - * 14-Jan-2006 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.*; - - -/** - * Helper class for {@link BellmanFordShortestPath}; not intended for general - * use. - */ -final class BellmanFordPathElement - extends AbstractPathElement -{ - //~ Instance fields -------------------------------------------------------- - - private double cost = 0; - private double epsilon; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a path element by concatenation of an edge to a path element. - * - * @param pathElement - * @param edge edge reaching the end vertex of the path element created. - * @param cost total cost of the created path element. - * @param epsilon tolerance factor. - */ - protected BellmanFordPathElement( - Graph graph, - BellmanFordPathElement pathElement, - E edge, - double cost, - double epsilon) - { - super(graph, pathElement, edge); - - this.cost = cost; - this.epsilon = epsilon; - } - - /** - * Copy constructor. - * - * @param original source to copy from - */ - BellmanFordPathElement(BellmanFordPathElement original) - { - super(original); - this.cost = original.cost; - this.epsilon = original.epsilon; - } - - /** - * Creates an empty path element. - * - * @param vertex end vertex of the path element. - * @param epsilon tolerance factor. - */ - protected BellmanFordPathElement(V vertex, double epsilon) - { - super(vertex); - - this.cost = 0; - this.epsilon = epsilon; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the total cost of the path element. - * - * @return . - */ - public double getCost() - { - return this.cost; - } - - /** - * Returns true if the path has been improved, - * false otherwise. We use an "epsilon" precision to check whether - * the cost has been improved (because of many roundings, a formula equal to - * 0 could unfortunately be evaluated to 10^-14). - * - * @param candidatePrevPathElement - * @param candidateEdge - * @param candidateCost - * - * @return . - */ - protected boolean improve( - BellmanFordPathElement candidatePrevPathElement, - E candidateEdge, - double candidateCost) - { - // to avoid improvement only due to rounding errors. - if (candidateCost < (getCost() - epsilon)) { - this.prevPathElement = candidatePrevPathElement; - this.prevEdge = candidateEdge; - this.cost = candidateCost; - this.nHops = candidatePrevPathElement.getHopCount() + 1; - - return true; - } else { - return false; - } - } -} - -// End BellmanFordPathElement.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordShortestPath.java deleted file mode 100644 index dcce9ffeeac..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BellmanFordShortestPath.java +++ /dev/null @@ -1,239 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BellmanFordShortestPath.java - * ------------------------- - * (C) Copyright 2006-2008, by France Telecom and Contributors. - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jan-2006 : Initial revision (GB); - * 14-Jan-2006 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Bellman-Ford - * algorithm: weights could be negative, paths could be constrained by a - * maximum number of edges. - */ -public class BellmanFordShortestPath -{ - //~ Static fields/initializers --------------------------------------------- - - private static final double DEFAULT_EPSILON = 0.000000001; - - //~ Instance fields -------------------------------------------------------- - - /** - * Graph on which shortest paths are searched. - */ - protected Graph graph; - - /** - * Start vertex. - */ - protected V startVertex; - - private BellmanFordIterator iter; - - /** - * Maximum number of edges of the calculated paths. - */ - private int nMaxHops; - - private int passNumber; - - private double epsilon; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates an object to calculate shortest paths between the start vertex - * and others vertices using the Bellman-Ford algorithm. - * - * @param graph - * @param startVertex - */ - public BellmanFordShortestPath(Graph graph, V startVertex) - { - this(graph, startVertex, graph.vertexSet().size() - 1); - } - - /** - * Creates an object to calculate shortest paths between the start vertex - * and others vertices using the Bellman-Ford algorithm. - * - * @param graph - * @param startVertex - * @param nMaxHops maximum number of edges of the calculated paths. - */ - public BellmanFordShortestPath( - Graph graph, - V startVertex, - int nMaxHops) - { - this(graph, startVertex, nMaxHops, DEFAULT_EPSILON); - } - - /** - * Creates an object to calculate shortest paths between the start vertex - * and others vertices using the Bellman-Ford algorithm. - * - * @param graph - * @param startVertex - * @param nMaxHops maximum number of edges of the calculated paths. - * @param epsilon tolerance factor. - */ - public BellmanFordShortestPath( - Graph graph, - V startVertex, - int nMaxHops, - double epsilon) - { - this.startVertex = startVertex; - this.nMaxHops = nMaxHops; - this.graph = graph; - this.passNumber = 1; - this.epsilon = epsilon; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @param endVertex end vertex. - * - * @return the cost of the shortest path between the start vertex and the - * end vertex. - */ - public double getCost(V endVertex) - { - assertGetPath(endVertex); - - lazyCalculate(); - - BellmanFordPathElement pathElement = - this.iter.getPathElement(endVertex); - - if (pathElement == null) { - return Double.POSITIVE_INFINITY; - } - - return pathElement.getCost(); - } - - /** - * @param endVertex end vertex. - * - * @return list of Edge, or null if no path exists between the - * start vertex and the end vertex. - */ - public List getPathEdgeList(V endVertex) - { - assertGetPath(endVertex); - - lazyCalculate(); - - BellmanFordPathElement pathElement = - this.iter.getPathElement(endVertex); - - if (pathElement == null) { - return null; - } - - return pathElement.createEdgeListPath(); - } - - private void assertGetPath(V endVertex) - { - if (endVertex.equals(this.startVertex)) { - throw new IllegalArgumentException( - "The end vertex is the same as the start vertex!"); - } - - if (!this.graph.containsVertex(endVertex)) { - throw new IllegalArgumentException( - "Graph must contain the end vertex!"); - } - } - - private void lazyCalculate() - { - if (this.iter == null) { - this.iter = - new BellmanFordIterator( - this.graph, - this.startVertex, - epsilon); - } - - // at the i-th pass the shortest paths with less (or equal) than i edges - // are calculated. - for ( - ; - (this.passNumber <= this.nMaxHops) && this.iter.hasNext(); - this.passNumber++) - { - this.iter.next(); - } - } - - /** - * Convenience method to find the shortest path via a single static method - * call. If you need a more advanced search (e.g. limited by hops, or - * computation of the path length), use the constructor instead. - * - * @param graph the graph to be searched - * @param startVertex the vertex at which the path should start - * @param endVertex the vertex at which the path should end - * - * @return List of Edges, or null if no path exists - */ - public static List findPathBetween( - Graph graph, - V startVertex, - V endVertex) - { - BellmanFordShortestPath alg = - new BellmanFordShortestPath( - graph, - startVertex); - - return alg.getPathEdgeList(endVertex); - } -} - -// End BellmanFordShortestPath.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BiconnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BiconnectivityInspector.java deleted file mode 100644 index 3e1c1ca490a..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BiconnectivityInspector.java +++ /dev/null @@ -1,138 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BiconnectivityInspector.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Inspects a graph for the biconnectivity property. See {@link - * BlockCutpointGraph} for more information. A biconnected graph has only one - * block (i.e. no cutpoints). - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class BiconnectivityInspector -{ - //~ Instance fields -------------------------------------------------------- - - private BlockCutpointGraph blockCutpointGraph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Running time = O(m) where m is the number of edges. - */ - public BiconnectivityInspector(UndirectedGraph graph) - { - super(); - this.blockCutpointGraph = new BlockCutpointGraph(graph); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the biconnected vertex-components of the graph. - */ - public Set> getBiconnectedVertexComponents() - { - Set> biconnectedVertexComponents = new HashSet>(); - for ( - Iterator> iter = - this.blockCutpointGraph.vertexSet().iterator(); - iter.hasNext();) - { - UndirectedGraph subgraph = iter.next(); - if (!subgraph.edgeSet().isEmpty()) { - biconnectedVertexComponents.add(subgraph.vertexSet()); - } - } - - return biconnectedVertexComponents; - } - - /** - * Returns the biconnected vertex-components containing the vertex. A - * biconnected vertex-component contains all the vertices in the component. - * A vertex which is not a cutpoint is contained in exactly one component. A - * cutpoint is contained is at least 2 components. - * - * @param vertex - * - * @return set of all biconnected vertex-components containing the vertex. - */ - public Set> getBiconnectedVertexComponents(V vertex) - { - Set> vertexComponents = new HashSet>(); - for ( - Iterator> iter = getBiconnectedVertexComponents().iterator(); - iter.hasNext();) - { - Set vertexComponent = iter.next(); - if (vertexComponent.contains(vertex)) { - vertexComponents.add(vertexComponent); - } - } - return vertexComponents; - } - - /** - * Returns the cutpoints of the graph. - */ - public Set getCutpoints() - { - return this.blockCutpointGraph.getCutpoints(); - } - - /** - * Returns true if the graph is biconnected (no cutpoint), - * false otherwise. - */ - public boolean isBiconnected() - { - return this.blockCutpointGraph.vertexSet().size() == 1; - } -} - -// End BiconnectivityInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BlockCutpointGraph.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BlockCutpointGraph.java deleted file mode 100644 index 5ea029c0505..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BlockCutpointGraph.java +++ /dev/null @@ -1,365 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BlockCutpointGraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * Definition of a block of a - * graph in MathWorld.
- *
Definition and lemma taken from the article - * Structure-Based Resilience Metrics for Service-Oriented Networks: - * - *

    - *
  • Definition 4.5 Let G(V; E) be a connected undirected graph. The - * block-cut point graph (BC graph) of G, denoted by GB(VB; EB), is the - * bipartite graph defined as follows. (a) VB has one node corresponding to each - * block and one node corresponding to each cut point of G. (b) Each edge fx; yg - * in EB joins a block node x to a cut point y if the block corresponding to x - * contains the cut point node corresponding to y.
  • - *
  • Lemma 4.4 Let G(V; E) be a connected undirected graph. (a) Each - * pair of blocks of G share at most one node, and that node is a cutpoint. (b) - * The BC graph of G is a tree in which each leaf node corresponds to a block of - * G.
  • - *
- * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class BlockCutpointGraph - extends SimpleGraph, DefaultEdge> -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -9101341117013163934L; - - //~ Instance fields -------------------------------------------------------- - - private Set cutpoints = new HashSet(); - - /** - * DFS (Depth-First-Search) tree. - */ - private DirectedGraph dfsTree; - - private UndirectedGraph graph; - - private int numOrder; - - private Deque stack = new ArrayDeque(); - - private Map>> vertex2biconnectedSubgraphs = - new HashMap>>(); - - private Map> vertex2block = - new HashMap>(); - - private Map vertex2numOrder = new HashMap(); - - //~ Constructors ----------------------------------------------------------- - - /** - * Running time = O(m) where m is the number of edges. - */ - public BlockCutpointGraph(UndirectedGraph graph) - { - super(DefaultEdge.class); - this.graph = graph; - - this.dfsTree = - new SimpleDirectedGraph( - DefaultEdge.class); - V s = graph.vertexSet().iterator().next(); - this.dfsTree.addVertex(s); - dfsVisit(s, s); - - if (this.dfsTree.edgesOf(s).size() > 1) { - this.cutpoints.add(s); - } else { - this.cutpoints.remove(s); - } - - for (Iterator iter = this.cutpoints.iterator(); iter.hasNext();) { - V cutpoint = iter.next(); - UndirectedGraph subgraph = - new SimpleGraph(this.graph.getEdgeFactory()); - subgraph.addVertex(cutpoint); - this.vertex2block.put(cutpoint, subgraph); - addVertex(subgraph); - Set> biconnectedSubgraphs = - getBiconnectedSubgraphs(cutpoint); - for ( - Iterator> iterator = - biconnectedSubgraphs.iterator(); - iterator.hasNext();) - { - UndirectedGraph biconnectedSubgraph = iterator.next(); - assert (vertexSet().contains(biconnectedSubgraph)); - addEdge(subgraph, biconnectedSubgraph); - } - } - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the vertex if vertex is a cutpoint, and otherwise returns the - * block (biconnected component) containing the vertex. - * - * @param vertex vertex in the initial graph. - */ - public UndirectedGraph getBlock(V vertex) - { - if (!this.graph.vertexSet().contains(vertex)) { - throw new IllegalArgumentException("No such vertex in the graph!"); - } - - return this.vertex2block.get(vertex); - } - - /** - * Returns the cutpoints of the initial graph. - */ - public Set getCutpoints() - { - return this.cutpoints; - } - - /** - * Returns true if the vertex is a cutpoint, false - * otherwise. - * - * @param vertex vertex in the initial graph. - */ - public boolean isCutpoint(V vertex) - { - if (!this.graph.vertexSet().contains(vertex)) { - throw new IllegalArgumentException("No such vertex in the graph!"); - } - - return this.cutpoints.contains(vertex); - } - - private void biconnectedComponentFinished(V s, V n) - { - this.cutpoints.add(s); - - Set vertexComponent = new HashSet(); - Set edgeComponent = new HashSet(); - BCGEdge edge = this.stack.removeLast(); - while ( - (getNumOrder(edge.getSource()) >= getNumOrder(n)) - && !this.stack.isEmpty()) - { - edgeComponent.add(edge); - - vertexComponent.add(edge.getSource()); - vertexComponent.add(edge.getTarget()); - - edge = this.stack.removeLast(); - } - edgeComponent.add(edge); - // edgeComponent is an equivalence class. - - vertexComponent.add(edge.getSource()); - vertexComponent.add(edge.getTarget()); - - VertexComponentForbiddenFunction mask = - new VertexComponentForbiddenFunction( - vertexComponent); - UndirectedGraph biconnectedSubgraph = - new UndirectedMaskSubgraph( - this.graph, - mask); - for (Iterator iter = vertexComponent.iterator(); iter.hasNext();) { - V vertex = iter.next(); - this.vertex2block.put(vertex, biconnectedSubgraph); - getBiconnectedSubgraphs(vertex).add(biconnectedSubgraph); - } - addVertex(biconnectedSubgraph); - } - - private int dfsVisit(V s, V father) - { - this.numOrder++; - int minS = this.numOrder; - setNumOrder(s, this.numOrder); - - for ( - Iterator iter = this.graph.edgesOf(s).iterator(); - iter.hasNext();) - { - E edge = iter.next(); - V n = Graphs.getOppositeVertex(this.graph, edge, s); - if (getNumOrder(n) == 0) { - this.dfsTree.addVertex(n); - BCGEdge dfsEdge = new BCGEdge(s, n); - this.dfsTree.addEdge(s, n, dfsEdge); - - this.stack.add(dfsEdge); - - // minimum of the traverse orders of the "attach points" of - // the vertex n. - int minN = dfsVisit(n, s); - minS = Math.min(minN, minS); - if (minN >= getNumOrder(s)) { - // s is a cutpoint. - // it has a son whose "attach depth" is greater or equal. - biconnectedComponentFinished(s, n); - } - } else if ((getNumOrder(n) < getNumOrder(s)) && !n.equals(father)) { - BCGEdge backwardEdge = new BCGEdge(s, n); - this.stack.add(backwardEdge); - - // n is an "attach point" of s. {s->n} is a backward edge. - minS = Math.min(getNumOrder(n), minS); - } - } - - // minimum of the traverse orders of the "attach points" of - // the vertex s. - return minS; - } - - /** - * Returns the biconnected components containing the vertex. A vertex which - * is not a cutpoint is contained in exactly one component. A cutpoint is - * contained is at least 2 components. - * - * @param vertex vertex in the initial graph. - */ - private Set> getBiconnectedSubgraphs(V vertex) - { - Set> biconnectedSubgraphs = - this.vertex2biconnectedSubgraphs.get(vertex); - if (biconnectedSubgraphs == null) { - biconnectedSubgraphs = new HashSet>(); - this.vertex2biconnectedSubgraphs.put(vertex, biconnectedSubgraphs); - } - return biconnectedSubgraphs; - } - - /** - * Returns the traverse order of the vertex in the DFS. - */ - private int getNumOrder(V vertex) - { - assert (vertex != null); - - Integer numOrder = this.vertex2numOrder.get(vertex); - if (numOrder == null) { - return 0; - } else { - return numOrder.intValue(); - } - } - - private void setNumOrder(V vertex, int numOrder) - { - this.vertex2numOrder.put(vertex, Integer.valueOf(numOrder)); - } - - //~ Inner Classes ---------------------------------------------------------- - - private class BCGEdge - extends DefaultEdge - { - /** - */ - private static final long serialVersionUID = -5115006161815760059L; - - private V source; - - private V target; - - public BCGEdge(V source, V target) - { - super(); - this.source = source; - this.target = target; - } - - public V getSource() - { - return this.source; - } - - public V getTarget() - { - return this.target; - } - } - - private class VertexComponentForbiddenFunction - implements MaskFunctor - { - private Set vertexComponent; - - public VertexComponentForbiddenFunction(Set vertexComponent) - { - this.vertexComponent = vertexComponent; - } - - public boolean isEdgeMasked(E edge) - { - return false; - } - - public boolean isVertexMasked(V vertex) - { - if (this.vertexComponent.contains(vertex)) { - // vertex belongs to component then we do not mask it. - return false; - } else { - return true; - } - } - } -} - -// End BlockCutpointGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/BronKerboschCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/BronKerboschCliqueFinder.java deleted file mode 100644 index bcce728a395..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/BronKerboschCliqueFinder.java +++ /dev/null @@ -1,198 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * BronKerboschCliqueFinder.java - * ------------------- - * (C) Copyright 2005-2008, by Ewgenij Proschak and Contributors. - * - * Original Author: Ewgenij Proschak - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 21-Jul-2005 : Initial revision (EP); - * 26-Jul-2005 : Cleaned up and checked in (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * This class implements Bron-Kerbosch clique detection algorithm as it is - * described in [Samudrala R.,Moult J.:A Graph-theoretic Algorithm for - * comparative Modeling of Protein Structure; J.Mol. Biol. (1998); vol 279; pp. - * 287-302] - * - * @author Ewgenij Proschak - */ -public class BronKerboschCliqueFinder -{ - //~ Instance fields -------------------------------------------------------- - - private final Graph graph; - - private Collection> cliques; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new clique finder. - * - * @param graph the graph in which cliques are to be found; graph must be - * simple - */ - public BronKerboschCliqueFinder(Graph graph) - { - this.graph = graph; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Finds all maximal cliques of the graph. A clique is maximal if it is - * impossible to enlarge it by adding another vertex from the graph. Note - * that a maximal clique is not necessarily the biggest clique in the graph. - * - * @return Collection of cliques (each of which is represented as a Set of - * vertices) - */ - public Collection> getAllMaximalCliques() - { - // TODO jvs 26-July-2005: assert that graph is simple - - cliques = new ArrayList>(); - List potential_clique = new ArrayList(); - List candidates = new ArrayList(); - List already_found = new ArrayList(); - candidates.addAll(graph.vertexSet()); - findCliques(potential_clique, candidates, already_found); - return cliques; - } - - /** - * Finds the biggest maximal cliques of the graph. - * - * @return Collection of cliques (each of which is represented as a Set of - * vertices) - */ - public Collection> getBiggestMaximalCliques() - { - // first, find all cliques - getAllMaximalCliques(); - - int maximum = 0; - Collection> biggest_cliques = new ArrayList>(); - for (Set clique : cliques) { - if (maximum < clique.size()) { - maximum = clique.size(); - } - } - for (Set clique : cliques) { - if (maximum == clique.size()) { - biggest_cliques.add(clique); - } - } - return biggest_cliques; - } - - private void findCliques( - List potential_clique, - List candidates, - List already_found) - { - List candidates_array = new ArrayList(candidates); - if (!end(candidates, already_found)) { - // for each candidate_node in candidates do - for (V candidate : candidates_array) { - List new_candidates = new ArrayList(); - List new_already_found = new ArrayList(); - - // move candidate node to potential_clique - potential_clique.add(candidate); - candidates.remove(candidate); - - // create new_candidates by removing nodes in candidates not - // connected to candidate node - for (V new_candidate : candidates) { - if (graph.containsEdge(candidate, new_candidate)) { - new_candidates.add(new_candidate); - } // of if - } // of for - - // create new_already_found by removing nodes in already_found - // not connected to candidate node - for (V new_found : already_found) { - if (graph.containsEdge(candidate, new_found)) { - new_already_found.add(new_found); - } // of if - } // of for - - // if new_candidates and new_already_found are empty - if (new_candidates.isEmpty() && new_already_found.isEmpty()) { - // potential_clique is maximal_clique - cliques.add(new HashSet(potential_clique)); - } // of if - else { - // recursive call - findCliques( - potential_clique, - new_candidates, - new_already_found); - } // of else - - // move candidate_node from potential_clique to already_found; - already_found.add(candidate); - potential_clique.remove(candidate); - } // of for - } // of if - } - - private boolean end(List candidates, List already_found) - { - // if a node in already_found is connected to all nodes in candidates - boolean end = false; - int edgecounter; - for (V found : already_found) { - edgecounter = 0; - for (V candidate : candidates) { - if (graph.containsEdge(found, candidate)) { - edgecounter++; - } // of if - } // of for - if (edgecounter == candidates.size()) { - end = true; - } - } // of for - return end; - } -} - -// End BronKerboschCliqueFinder.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/ChromaticNumber.java b/jgrapht-core/src/main/java/org/jgrapht/alg/ChromaticNumber.java deleted file mode 100644 index 84a73295d3e..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/ChromaticNumber.java +++ /dev/null @@ -1,147 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * ChromaticNumber.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): gpaschos@netscape.net, harshalv@telenav.com - * - * $Id$ - * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.alg.util.*; -import org.jgrapht.graph.*; - - -/** - * Allows the - * chromatic number of a graph to be calculated. This is the minimal number - * of colors needed to color each vertex such that no two adjacent vertices - * share the same color. This algorithm will not find the true chromatic number, - * since this is an NP-complete problem. So, a greedy algorithm will find an - * approximate chromatic number. - * - * @author Andrew Newell - * @since Dec 21, 2008 - */ -public abstract class ChromaticNumber -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Finds the number of colors required for a greedy coloring of the graph. - * - * @param g an undirected graph to find the chromatic number of - * - * @return integer the approximate chromatic number from the greedy - * algorithm - */ - public static int findGreedyChromaticNumber(UndirectedGraph g) - { - Map> coloredGroups = findGreedyColoredGroups(g); - return coloredGroups.keySet().size(); - } - - /** - * Finds a greedy coloring of the graph. - * - * @param g an undirected graph for which to find the coloring - */ - public static Map> findGreedyColoredGroups( - UndirectedGraph g) - { - // A copy of the graph is made, so that elements of the graph may be - // removed to carry out the algorithm - UndirectedGraph sg = new UndirectedSubgraph(g, null, null); - - // The Vertices will be sorted in decreasing order by degree, so that - // higher degree vertices have priority to be colored first - VertexDegreeComparator comp = - new VertexDegreeComparator(sg); - List sortedVertices = new LinkedList(sg.vertexSet()); - Collections.sort(sortedVertices, comp); - Collections.reverse(sortedVertices); - - int color; - - // create a map which will hold color as key and Set as value - Map> coloredGroups = new HashMap>(); - - // We'll attempt to color each vertex with a single color each - // iteration, and these vertices will be removed from the graph at the - // end of each iteration - for (color = 0; sg.vertexSet().size() > 0; color++) { - // This set will contain vertices that are colored with the - // current color of this iteration - Set currentColor = new HashSet(); - for ( - Iterator iter = sortedVertices.iterator(); - iter.hasNext();) - { - V v = iter.next(); - - // Add new vertices to be colored as long as they are not - // adjacent with any other vertex that has already been colored - // with the current color - boolean flag = true; - for ( - Iterator innerIter = currentColor.iterator(); - innerIter.hasNext();) - { - V temp = innerIter.next(); - if (sg.containsEdge(temp, v)) { - flag = false; - break; - } - } - if (flag) { - currentColor.add(v); - iter.remove(); - } - } - - // Add all these vertices as a group for this color - coloredGroups.put(color, currentColor); - - // Remove vertices from the graph and then repeat the process for - // the next iteration - sg.removeAllVertices(currentColor); - } - return coloredGroups; - } -} - -// End ChromaticNumber.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/ConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/ConnectivityInspector.java deleted file mode 100644 index d242878d912..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/ConnectivityInspector.java +++ /dev/null @@ -1,302 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * ConnectivityInspector.java - * -------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): John V. Sichi - * Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 06-Aug-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 07-Jun-2005 : Made generic (CH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.event.*; -import org.jgrapht.graph.*; -import org.jgrapht.traverse.*; - - -/** - * 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 StrongConnectivityInspector} 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.

- * - * @author Barak Naveh - * @author John V. Sichi - * @since Aug 6, 2003 - */ -public class ConnectivityInspector - implements GraphListener -{ - //~ Instance fields -------------------------------------------------------- - - List> connectedSets; - Map> vertexToConnectedSet; - private Graph graph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a connectivity inspector for the specified undirected graph. - * - * @param g the graph for which a connectivity inspector to be created. - */ - public ConnectivityInspector(UndirectedGraph g) - { - init(); - this.graph = g; - } - - /** - * Creates a connectivity inspector for the specified directed graph. - * - * @param g the graph for which a connectivity inspector to be created. - */ - public ConnectivityInspector(DirectedGraph g) - { - init(); - this.graph = new AsUndirectedGraph(g); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Test if the inspected graph is connected. An empty graph is not - * considered connected. - * - * @return true if and only if inspected graph is connected. - */ - public boolean isGraphConnected() - { - 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) - */ - public void edgeAdded(GraphEdgeChangeEvent e) - { - init(); // for now invalidate cached results, in the future need to - // amend them. - } - - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) - { - init(); // for now invalidate cached results, in the future need to - // amend them. - } - - /** - * Tests if there is a path from the specified source vertex to the - * specified target vertices. For a directed graph, direction is ignored for - * this interpretation of path. - * - *

Note: Future versions of this method might not ignore edge directions - * for directed graphs.

- * - * @param sourceVertex one end of the path. - * @param targetVertex another end of the path. - * - * @return true if and only if there is a path from the source - * vertex to the target vertex. - */ - public boolean pathExists(V sourceVertex, V targetVertex) - { - /* - * TODO: Ignoring edge direction for directed graph may be - * confusing. For directed graphs, consider Dijkstra's algorithm. - */ - Set sourceSet = connectedSetOf(sourceVertex); - - return sourceSet.contains(targetVertex); - } - - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ - public void vertexAdded(GraphVertexChangeEvent e) - { - init(); // for now invalidate cached results, in the future need to - // amend them. - } - - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ - public void vertexRemoved(GraphVertexChangeEvent e) - { - init(); // for now invalidate cached results, in the future need to - // amend them. - } - - private void init() - { - connectedSets = null; - vertexToConnectedSet = new HashMap>(); - } - - private List> lazyFindConnectedSets() - { - if (connectedSets == null) { - connectedSets = new ArrayList>(); - - Set vertexSet = graph.vertexSet(); - - if (vertexSet.size() > 0) { - BreadthFirstIterator i = - new BreadthFirstIterator(graph, null); - i.addTraversalListener(new MyTraversalListener()); - - while (i.hasNext()) { - i.next(); - } - } - } - - return connectedSets; - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * A traversal listener that groups all vertices according to to their - * containing connected set. - * - * @author Barak Naveh - * @since Aug 6, 2003 - */ - private class MyTraversalListener - extends TraversalListenerAdapter - { - private Set currentConnectedSet; - - /** - * @see TraversalListenerAdapter#connectedComponentFinished(ConnectedComponentTraversalEvent) - */ - public void connectedComponentFinished( - ConnectedComponentTraversalEvent e) - { - connectedSets.add(currentConnectedSet); - } - - /** - * @see TraversalListenerAdapter#connectedComponentStarted(ConnectedComponentTraversalEvent) - */ - public void connectedComponentStarted( - ConnectedComponentTraversalEvent e) - { - currentConnectedSet = new HashSet(); - } - - /** - * @see TraversalListenerAdapter#vertexTraversed(VertexTraversalEvent) - */ - public void vertexTraversed(VertexTraversalEvent e) - { - V v = e.getVertex(); - currentConnectedSet.add(v); - vertexToConnectedSet.put(v, currentConnectedSet); - } - } -} - -// End ConnectivityInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/CycleDetector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/CycleDetector.java deleted file mode 100644 index 3cd1a60dbb2..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/CycleDetector.java +++ /dev/null @@ -1,271 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * CycleDetector.java - * ------------------ - * (C) Copyright 2004-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 16-Sept-2004 : Initial revision (JVS); - * 07-Jun-2005 : Made generic (CH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.traverse.*; - - -/** - * 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. - * - * @author John V. Sichi - * @since Sept 16, 2004 - */ -public class CycleDetector -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Graph on which cycle detection is being performed. - */ - DirectedGraph graph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a cycle detector for the specified graph. Currently only directed - * graphs are supported. - * - * @param graph the DirectedGraph in which to detect cycles - */ - public CycleDetector(DirectedGraph graph) - { - this.graph = graph; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * 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 StrongConnectivityInspector instead. - StrongConnectivityInspector inspector = - new StrongConnectivityInspector(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 HashSet(); - 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 - * StrongConnectivityInspector 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 HashSet(); - execute(set, v); - - return set; - } - - private void execute(Set s, V v) - { - ProbeIterator iter = new ProbeIterator(s, v); - - while (iter.hasNext()) { - iter.next(); - } - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * 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 class ProbeIterator - extends DepthFirstIterator - { - private List path; - private Set cycleSet; - private V root; - - ProbeIterator(Set cycleSet, V startVertex) - { - super(graph, startVertex); - root = startVertex; - this.cycleSet = cycleSet; - path = new ArrayList(); - } - - /** - * {@inheritDoc} - */ - 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} - */ - 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; - } - } -} - -// End CycleDetector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/DijkstraShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/DijkstraShortestPath.java deleted file mode 100644 index 43a6532e587..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/DijkstraShortestPath.java +++ /dev/null @@ -1,218 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * DijkstraShortestPath.java - * ------------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 02-Sep-2003 : Initial revision (JVS); - * 29-May-2005 : Make non-static and add radius support (JVS); - * 07-Jun-2005 : Made generic (CH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; -import org.jgrapht.traverse.*; - - -/** - * An implementation of Dijkstra's - * shortest path algorithm using ClosestFirstIterator. - * - * @author John V. Sichi - * @since Sep 2, 2003 - */ -public final class DijkstraShortestPath -{ - //~ Instance fields -------------------------------------------------------- - - private GraphPath path; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates and executes a new DijkstraShortestPath algorithm instance. An - * instance is only good for a single search; after construction, it can be - * accessed to retrieve information about the path found. - * - * @param graph the graph to be searched - * @param startVertex the vertex at which the path should start - * @param endVertex the vertex at which the path should end - */ - public DijkstraShortestPath(Graph graph, - V startVertex, - V endVertex) - { - this(graph, startVertex, endVertex, Double.POSITIVE_INFINITY); - } - - /** - * Creates and executes a new DijkstraShortestPath algorithm instance. An - * instance is only good for a single search; after construction, it can be - * accessed to retrieve information about the path found. - * - * @param graph the graph to be searched - * @param startVertex the vertex at which the path should start - * @param endVertex the vertex at which the path should end - * @param radius limit on path length, or Double.POSITIVE_INFINITY for - * unbounded search - */ - public DijkstraShortestPath( - Graph graph, - V startVertex, - V endVertex, - double radius) - { - if (!graph.containsVertex(endVertex)) { - throw new IllegalArgumentException( - "graph must contain the end vertex"); - } - - ClosestFirstIterator iter = - new ClosestFirstIterator(graph, startVertex, radius); - - while (iter.hasNext()) { - V vertex = iter.next(); - - if (vertex.equals(endVertex)) { - createEdgeList(graph, iter, startVertex, endVertex); - return; - } - } - - path = null; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Return the edges making up the path found. - * - * @return List of Edges, or null if no path exists - */ - public List getPathEdgeList() - { - if (path == null) { - return null; - } else { - return path.getEdgeList(); - } - } - - /** - * Return the path found. - * - * @return path representation, or null if no path exists - */ - public GraphPath getPath() - { - return path; - } - - /** - * Return the length of the path found. - * - * @return path length, or Double.POSITIVE_INFINITY if no path exists - */ - public double getPathLength() - { - if (path == null) { - return Double.POSITIVE_INFINITY; - } else { - return path.getWeight(); - } - } - - /** - * Convenience method to find the shortest path via a single static method - * call. If you need a more advanced search (e.g. limited by radius, or - * computation of the path length), use the constructor instead. - * - * @param graph the graph to be searched - * @param startVertex the vertex at which the path should start - * @param endVertex the vertex at which the path should end - * - * @return List of Edges, or null if no path exists - */ - public static List findPathBetween( - Graph graph, - V startVertex, - V endVertex) - { - DijkstraShortestPath alg = - new DijkstraShortestPath( - graph, - startVertex, - endVertex); - - return alg.getPathEdgeList(); - } - - private void createEdgeList( - Graph graph, - ClosestFirstIterator iter, - V startVertex, - V endVertex) - { - List edgeList = new ArrayList(); - - V v = endVertex; - - while (true) { - E edge = iter.getSpanningTreeEdge(v); - - if (edge == null) { - break; - } - - edgeList.add(edge); - v = Graphs.getOppositeVertex(graph, edge, v); - } - - Collections.reverse(edgeList); - double pathLength = iter.getShortestPathLength(endVertex); - path = - new GraphPathImpl( - graph, - startVertex, - endVertex, - edgeList, - pathLength); - } -} - -// End DijkstraShortestPath.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/DirectedNeighborIndex.java b/jgrapht-core/src/main/java/org/jgrapht/alg/DirectedNeighborIndex.java deleted file mode 100644 index fdf755711e7..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/DirectedNeighborIndex.java +++ /dev/null @@ -1,233 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * DirectedNeighborIndex.java - * -------------------------- - * (C) Copyright 2005-2008, by Charles Fry and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 13-Dec-2005 : Initial revision (CF); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.alg.NeighborIndex.*; -import org.jgrapht.event.*; - - -/** - * 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. - * - *

A vertex's neighbors are cached the first time they are asked for (i.e. - * the index is built on demand). The index will only be updated automatically - * if it is added to the associated graph as a listener. If it is added as a - * listener to a graph other than the one it indexes, results are undefined.

- * - * @author Charles Fry - * @since Dec 13, 2005 - */ -public class DirectedNeighborIndex - implements GraphListener -{ - //~ Instance fields -------------------------------------------------------- - - Map> predecessorMap = new HashMap>(); - Map> successorMap = new HashMap>(); - private DirectedGraph graph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a neighbor index for the specified directed graph. - * - * @param g the graph for which a neighbor index is to be created. - */ - public DirectedNeighborIndex(DirectedGraph g) - { - graph = g; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the set of vertices which are the predecessors of a specified - * vertex. The returned set is backed by the index, and will be updated when - * the graph changes as long as the index has been added as a listener to - * the graph. - * - * @param v the vertex whose predecessors are desired - * - * @return all unique predecessors of the specified vertex - */ - public Set predecessorsOf(V v) - { - return getPredecessors(v).getNeighbors(); - } - - /** - * Returns the set of vertices which are the predecessors of a specified - * vertex. If the graph is a multigraph, vertices may appear more than once - * in the returned list. Because a list of predecessors 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 #predecessorsOf(Object)} unless duplicate neighbors are required. - * - * @param v the vertex whose predecessors are desired - * - * @return all predecessors of the specified vertex - */ - public List predecessorListOf(V v) - { - return getPredecessors(v).getNeighborList(); - } - - /** - * Returns the set of vertices which are the successors of a specified - * vertex. The returned set is backed by the index, and will be updated when - * the graph changes as long as the index has been added as a listener to - * the graph. - * - * @param v the vertex whose successors are desired - * - * @return all unique successors of the specified vertex - */ - public Set successorsOf(V v) - { - return getSuccessors(v).getNeighbors(); - } - - /** - * Returns the set of vertices which are the successors of a specified - * vertex. If the graph is a multigraph, vertices may appear more than once - * in the returned list. Because a list of successors 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 - * #successorsOf(Object)} unless duplicate neighbors are required. - * - * @param v the vertex whose successors are desired - * - * @return all successors of the specified vertex - */ - public List successorListOf(V v) - { - return getSuccessors(v).getNeighborList(); - } - - /** - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public void edgeAdded(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - V source = graph.getEdgeSource(edge); - V target = graph.getEdgeTarget(edge); - - // if a map does not already contain an entry, - // then skip addNeighbor, since instantiating the map - // will take care of processing the edge (which has already - // been added) - - if (successorMap.containsKey(source)) { - getSuccessors(source).addNeighbor(target); - } else { - getSuccessors(source); - } - if (predecessorMap.containsKey(target)) { - getPredecessors(target).addNeighbor(source); - } else { - getPredecessors(target); - } - } - - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - V source = e.getEdgeSource(); - V target = e.getEdgeTarget(); - if (successorMap.containsKey(source)) { - successorMap.get(source).removeNeighbor(target); - } - if (predecessorMap.containsKey(target)) { - predecessorMap.get(target).removeNeighbor(source); - } - } - - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ - public void vertexAdded(GraphVertexChangeEvent e) - { - // nothing to cache until there are edges - } - - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ - public void vertexRemoved(GraphVertexChangeEvent e) - { - predecessorMap.remove(e.getVertex()); - successorMap.remove(e.getVertex()); - } - - private Neighbors getPredecessors(V v) - { - Neighbors neighbors = predecessorMap.get(v); - if (neighbors == null) { - neighbors = - new Neighbors(v, - Graphs.predecessorListOf(graph, v)); - predecessorMap.put(v, neighbors); - } - return neighbors; - } - - private Neighbors getSuccessors(V v) - { - Neighbors neighbors = successorMap.get(v); - if (neighbors == null) { - neighbors = - new Neighbors(v, - Graphs.successorListOf(graph, v)); - successorMap.put(v, neighbors); - } - return neighbors; - } -} - -// End DirectedNeighborIndex.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsBlossomShrinking.java b/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsBlossomShrinking.java deleted file mode 100644 index 866b4d37f99..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsBlossomShrinking.java +++ /dev/null @@ -1,194 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2012, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * EdmondsBlossomShrinking.java - * ------------------------- - * (C) Copyright 2012-2012, by Alejandro Ramon Lopez del Huerto and Contributors. - * - * Original Author: Alejandro Ramon Lopez del Huerto - * Contributor(s): - * - * Changes - * ------- - * 24-Jan-2012 : Initial revision (ARLH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - -/** - * An implementation of Edmonds Blossom Shrinking algorithm for constructing - * maximum matchings on graphs. The algorithm runs in time O(V^4). - * - * @author Alejandro R. Lopez del Huerto - * @since Jan 24, 2012 - */ -public class EdmondsBlossomShrinking -{ - // ~ Instance fields - // -------------------------------------------------------- - - private Map match; - private Map p; - private Map base; - private Queue q; - private Set used; - private Set blossom; - - // ~ Methods - // ---------------------------------------------------------------- - - /** - * Runs the algorithm on the input graph and returns the match edge set. - * - * @param g - * The graph to be matched - * @return set of Edges - */ - public Set findMatch(final UndirectedGraph g) - { - Set result = new ArrayUnenforcedSet(); - match = new HashMap(); - p = new HashMap(); - q = new ArrayDeque(); - base = new HashMap(); - used = new HashSet(); - blossom = new HashSet(); - - for (V i : g.vertexSet()) { - if (!match.containsKey(i)) { - V v = findPath(g, i); - while (v != null) { - V pv = p.get(v); - V ppv = match.get(pv); - match.put(v, pv); - match.put(pv, v); - v = ppv; - } - } - } - - Set seen = new HashSet(); - for (V v : g.vertexSet()) { - if (!seen.contains(v) && match.containsKey(v)) { - seen.add(v); - seen.add(match.get(v)); - result.add(g.getEdge(v, match.get(v))); - } - } - - return result; - } - - private V findPath(UndirectedGraph g, V root) - { - used.clear(); - p.clear(); - base.clear(); - - for (V i : g.vertexSet()) { - base.put(i, i); - } - - used.add(root); - q.add(root); - while (!q.isEmpty()) { - V v = q.remove(); - for (V to : g.vertexSet()) { - if (!g.containsEdge(v, to)) { - continue; - } - - if ((base.get(v) == base.get(to)) || (match.get(v) == to)) { - continue; - } - if (to == root || (match.containsKey(to)) - && (p.containsKey(match.get(to)))) { - V curbase = lca(g, v, to); - blossom.clear(); - markPath(g, v, curbase, to); - markPath(g, to, curbase, v); - - for (V i : g.vertexSet()) { - if (base.containsKey(i) - && blossom.contains(base.get(i))) - { - base.put(i, curbase); - if (!used.contains(i)) { - used.add(i); - q.add(i); - } - } - } - } else if (!p.containsKey(to)) { - p.put(to, v); - if (!match.containsKey(to)) { - return to; - } - to = match.get(to); - used.add(to); - q.add(to); - } - } - } - return null; - } - - private void markPath(UndirectedGraph g, V v, V b, V children) - { - while (base.get(v) != b) { - blossom.add(base.get(v)); - blossom.add(base.get(match.get(v))); - p.put(v, children); - children = match.get(v); - v = p.get(match.get(v)); - } - } - - private V lca(UndirectedGraph g, V a, V b) - { - Set seen = new HashSet(); - for (;;) { - a = base.get(a); - seen.add(a); - if (!match.containsKey(a)) { - break; - } - a = p.get(match.get(a)); - } - for (;;) { - b = base.get(b); - if (seen.contains(b)) { - return b; - } - b = p.get(match.get(b)); - } - } - -} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsKarpMaximumFlow.java b/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsKarpMaximumFlow.java deleted file mode 100644 index 85d80d1de4f..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/EdmondsKarpMaximumFlow.java +++ /dev/null @@ -1,361 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EdmondsKarpMaximumFlow.java - * ----------------- - * (C) Copyright 2008-2008, by Ilya Razenshteyn and Contributors. - * - * Original Author: Ilya Razenshteyn - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A flow network is a - * directed graph where each edge has a capacity and each edge receives a flow. - * The amount of flow on an edge can not exceed the capacity of the edge (note, - * that all capacities must be non-negative). A flow must satisfy the - * restriction that the amount of flow into a vertex equals the amount of flow - * out of it, except when it is a source, which "produces" flow, or sink, which - * "consumes" flow. - * - *

This class computes maximum flow in a network using Edmonds-Karp - * algorithm. Be careful: for large networks this algorithm may consume - * significant amount of time (its upper-bound complexity is O(VE^2), where V - - * amount of vertices, E - amount of edges in the network). - * - *

For more details see Andrew V. Goldberg's Combinatorial Optimization - * (Lecture Notes). - */ -public final class EdmondsKarpMaximumFlow -{ - //~ Static fields/initializers --------------------------------------------- - - /** - * Default tolerance. - */ - public static final double DEFAULT_EPSILON = 0.000000001; - - //~ Instance fields -------------------------------------------------------- - - private DirectedGraph network; // our network - private double epsilon; // tolerance (DEFAULT_EPSILON or user-defined) - private int currentSource; // current source vertex - private int currentSink; // current sink vertex - private Map maximumFlow; // current maximum flow - private Double maximumFlowValue; // current maximum flow value - private int numNodes; // number of nodes in the network - private Map indexer; // mapping from vertices to their indexes - // in the internal representation - private List nodes; // internal representation of the network - - //~ Constructors ----------------------------------------------------------- - - /** - * 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 EdmondsKarpMaximumFlow(DirectedGraph 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 EdmondsKarpMaximumFlow(DirectedGraph network, - double epsilon) - { - 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)"); - } - } - - this.network = network; - this.epsilon = epsilon; - - currentSource = -1; - currentSink = -1; - maximumFlow = null; - maximumFlowValue = null; - - buildInternalNetwork(); - } - - //~ Methods ---------------------------------------------------------------- - - // converting the original network into internal more convenient format - private void buildInternalNetwork() - { - numNodes = network.vertexSet().size(); - nodes = new ArrayList(); - Iterator it = network.vertexSet().iterator(); - indexer = new HashMap(); - for (int i = 0; i < numNodes; i++) { - V currentNode = it.next(); - nodes.add(new Node(currentNode)); - indexer.put(currentNode, i); - } - for (int i = 0; i < numNodes; i++) { - V we = nodes.get(i).prototype; - for (E e : network.outgoingEdgesOf(we)) { - V he = network.getEdgeTarget(e); - int j = indexer.get(he); - Arc e1 = new Arc(i, j, network.getEdgeWeight(e), e); - Arc e2 = new Arc(j, i, 0.0, null); - e1.reversed = e2; - e2.reversed = e1; - nodes.get(i).outgoingArcs.add(e1); - nodes.get(j).outgoingArcs.add(e2); - } - } - } - - /** - * 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 - */ - public void calculateMaximumFlow( - V source, - V sink) - { - 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 = indexer.get(source); - currentSink = indexer.get(sink); - - for (int i = 0; i < numNodes; i++) { - for (Arc currentArc : nodes.get(i).outgoingArcs) { - currentArc.flow = 0.0; - } - } - maximumFlowValue = 0.0; - for (;;) { - breadthFirstSearch(); - if (!nodes.get(currentSink).visited) { - maximumFlow = new HashMap(); - for (int i = 0; i < numNodes; i++) { - for (Arc currentArc : nodes.get(i).outgoingArcs) { - if (currentArc.prototype != null) { - maximumFlow.put( - currentArc.prototype, - currentArc.flow); - } - } - } - return; - } - augmentFlow(); - } - } - - private void breadthFirstSearch() - { - for (int i = 0; i < numNodes; i++) { - nodes.get(i).visited = false; - } - Queue queue = new LinkedList(); - queue.offer(currentSource); - nodes.get(currentSource).visited = true; - nodes.get(currentSource).flowAmount = Double.POSITIVE_INFINITY; - while (queue.size() != 0) { - int currentNode = queue.poll(); - for (Arc currentArc : nodes.get(currentNode).outgoingArcs) { - if ((currentArc.flow + epsilon) < currentArc.capacity) { - if (!nodes.get(currentArc.head).visited) { - nodes.get(currentArc.head).visited = true; - nodes.get(currentArc.head).flowAmount = - Math.min( - nodes.get(currentNode).flowAmount, - currentArc.capacity - currentArc.flow); - nodes.get(currentArc.head).lastArc = currentArc; - queue.add(currentArc.head); - } - } - } - } - } - - private void augmentFlow() - { - double deltaFlow = nodes.get(currentSink).flowAmount; - maximumFlowValue += deltaFlow; - int currentNode = currentSink; - while (currentNode != currentSource) { - nodes.get(currentNode).lastArc.flow += deltaFlow; - nodes.get(currentNode).lastArc.reversed.flow -= deltaFlow; - currentNode = nodes.get(currentNode).lastArc.tail; - } - } - - /** - * Returns maximum flow value, that was calculated during last - * calculateMaximumFlow call, or null, if there was no - * calculateMaximumFlow calls. - * - * @return maximum flow value - */ - public Double getMaximumFlowValue() - { - return maximumFlowValue; - } - - /** - * 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 getMaximumFlow() - { - if (maximumFlow == null) { - return null; - } - return Collections.unmodifiableMap(maximumFlow); - } - - /** - * Returns current source vertex, or null if there was no - * calculateMaximumFlow calls. - * - * @return current source - */ - public V getCurrentSource() - { - if (currentSource == -1) { - return null; - } - return nodes.get(currentSource).prototype; - } - - /** - * Returns current sink vertex, or null if there was no - * calculateMaximumFlow calls. - * - * @return current sink - */ - public V getCurrentSink() - { - if (currentSink == -1) { - return null; - } - return nodes.get(currentSink).prototype; - } - - //~ Inner Classes ---------------------------------------------------------- - - // class used for internal representation of network - class Node - { - V prototype; // corresponding node in the original network - List outgoingArcs = new ArrayList(); // list of outgoing arcs - // in the residual - // network - boolean visited; // this mark is used during BFS to mark visited nodes - Arc lastArc; // last arc in the shortest path - double flowAmount; // amount of flow, we are able to push here - - Node( - V prototype) - { - this.prototype = prototype; - } - } - - // class used for internal representation of network - class Arc - { - int tail; // "from" - int head; // "to" - double capacity; // capacity (can be zero) - double flow; // current flow (can be negative) - Arc reversed; // for each arc in the original network we are to create - // reversed arc - E prototype; // corresponding edge in the original network, can be null, - // if it is reversed arc - - Arc( - int tail, - int head, - double capacity, - E prototype) - { - this.tail = tail; - this.head = head; - this.capacity = capacity; - this.prototype = prototype; - } - } -} - -// End EdmondsKarpMaximumFlow.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/EulerianCircuit.java b/jgrapht-core/src/main/java/org/jgrapht/alg/EulerianCircuit.java deleted file mode 100644 index 0ea4e8b8dd3..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/EulerianCircuit.java +++ /dev/null @@ -1,148 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * EulerianCircuit.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * This algorithm will check whether a graph is Eulerian (hence it contains an - * Eulerian - * circuit). Also, if a graph is Eulerian, the caller can obtain a list of - * vertices making up the Eulerian circuit. An Eulerian circuit is a circuit - * which traverses each edge exactly once. - * - * @author Andrew Newell - * @since Dec 21, 2008 - */ -public abstract class EulerianCircuit -{ - //~ Methods ---------------------------------------------------------------- - - /** - * This method will check whether the graph passed in is Eulerian or not. - * - * @param g The graph to be checked - * - * @return true for Eulerian and false for non-Eulerian - */ - public static boolean isEulerian(UndirectedGraph g) - { - // If the graph is not connected, then no Eulerian circuit exists - if (!(new ConnectivityInspector(g)).isGraphConnected()) { - return false; - } - - // A graph is Eulerian if and only if all vertices have even degree - // So, this code will check for that - Iterator iter = g.vertexSet().iterator(); - while (iter.hasNext()) { - V v = iter.next(); - if ((g.degreeOf(v) % 2) == 1) { - return false; - } - } - return true; - } - - /** - * This method will return a list of vertices which represents the Eulerian - * circuit of the graph. - * - * @param g The graph to find an Eulerian circuit - * - * @return null if no Eulerian circuit exists, or a list of vertices - * representing the Eulerian circuit if one does exist - */ - public static List getEulerianCircuitVertices( - UndirectedGraph g) - { - // If the graph is not Eulerian then just return a null since no - // Eulerian circuit exists - if (!isEulerian(g)) { - return null; - } - - // The circuit will be represented by a linked list - List path = new LinkedList(); - UndirectedGraph sg = new UndirectedSubgraph(g, null, null); - path.add(sg.vertexSet().iterator().next()); - - // Algorithm for finding an Eulerian circuit Basically this will find an - // arbitrary circuit, then it will find another arbitrary circuit until - // every edge has been traversed - while (sg.edgeSet().size() > 0) { - V v = null; - - // Find a vertex which has an edge that hasn't been traversed yet, - // and keep its index position in the circuit list - int index = 0; - for (Iterator iter = path.iterator(); iter.hasNext(); index++) { - v = iter.next(); - if (sg.degreeOf(v) > 0) { - break; - } - } - - // Finds an arbitrary circuit of the current vertex and - // appends this into the circuit list - while (sg.degreeOf(v) > 0) { - for ( - Iterator iter = sg.vertexSet().iterator(); - iter.hasNext();) - { - V temp = iter.next(); - if (sg.containsEdge(v, temp)) { - path.add(index, temp); - sg.removeEdge(v, temp); - v = temp; - break; - } - } - } - } - return path; - } -} - -// End EulerianCircuit.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/FloydWarshallShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/FloydWarshallShortestPaths.java deleted file mode 100644 index 355a709b0ae..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/FloydWarshallShortestPaths.java +++ /dev/null @@ -1,295 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * FloydWarshallShortestPaths.java - * ------------------------- - * (C) Copyright 2009-2009, by Tom Larkworthy and Contributors - * - * Original Author: Tom Larkworthy - * Contributor(s): Soren Davidsen - * - * $Id$ - * - * Changes - * ------- - * 29-Jun-2009 : Initial revision (TL); - * 03-Dec-2009 : Optimized and enhanced version (SD); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; -import org.jgrapht.util.*; - - -/** - * The - * Floyd-Warshall algorithm finds all shortest paths (all n^2 of them) in - * O(n^3) time. This also works out the graph diameter during the process. - * - * @author Tom Larkworthy - * @author Soren Davidsen - */ -public class FloydWarshallShortestPaths -{ - //~ Instance fields -------------------------------------------------------- - - private Graph graph; - private List vertices; - private int nShortestPaths = 0; - private double diameter = 0.0; - private double [][] d = null; - private int [][] backtrace = null; - private Map, GraphPath> paths = null; - - //~ Constructors ----------------------------------------------------------- - - public FloydWarshallShortestPaths(Graph graph) - { - this.graph = graph; - this.vertices = new ArrayList(graph.vertexSet()); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @return the graph on which this algorithm operates - */ - public Graph getGraph() - { - return graph; - } - - /** - * @return total number of shortest paths - */ - public int getShortestPathsCount() - { - lazyCalculatePaths(); - return nShortestPaths; - } - - /** - * Calculates the matrix of all shortest paths, along with the diameter, but - * does not populate the paths map. - */ - private void lazyCalculateMatrix() - { - if (d != null) { - // already done - return; - } - - int n = vertices.size(); - - // init the backtrace matrix - backtrace = new int[n][n]; - for (int i = 0; i < n; i++) { - Arrays.fill(backtrace[i], -1); - } - - // 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 - Set edges = graph.edgeSet(); - for (E edge : edges) { - V v1 = graph.getEdgeSource(edge); - V v2 = graph.getEdgeTarget(edge); - - int v_1 = vertices.indexOf(v1); - int v_2 = vertices.indexOf(v2); - - d[v_1][v_2] = graph.getEdgeWeight(edge); - if (!(graph instanceof DirectedGraph)) { - d[v_2][v_1] = graph.getEdgeWeight(edge); - } - } - - // run fw alg - for (int k = 0; k < n; k++) { - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - double ik_kj = d[i][k] + d[k][j]; - if (ik_kj < d[i][j]) { - d[i][j] = ik_kj; - backtrace[i][j] = k; - diameter = (diameter > d[i][j]) ? diameter : d[i][j]; - } - } - } - } - } - - /** - * Get the length of a shortest path. - * - * @param a first vertex - * @param b second vertex - * - * @return shortest distance between a and b - */ - public double shortestDistance(V a, V b) - { - lazyCalculateMatrix(); - - return d[vertices.indexOf(a)][vertices.indexOf(b)]; - } - - /** - * @return the diameter (longest of all the shortest paths) computed for the - * graph - */ - public double getDiameter() - { - lazyCalculateMatrix(); - return diameter; - } - - private void shortestPathRecur(List edges, int v_a, int v_b) - { - int k = backtrace[v_a][v_b]; - if (k == -1) { - E edge = graph.getEdge(vertices.get(v_a), vertices.get(v_b)); - if (edge != null) { - edges.add(edge); - } - } else { - shortestPathRecur(edges, v_a, k); - shortestPathRecur(edges, k, v_b); - } - } - - /** - * Get the shortest path between two vertices. Note: The paths are - * calculated using a recursive algorithm. It *will* give problems on paths - * longer than the stack allows. - * - * @param a From vertice - * @param b To vertice - * - * @return the path, or null if none found - */ - public GraphPath getShortestPath(V a, V b) - { - lazyCalculatePaths(); - return getShortestPathImpl(a, b); - } - - private GraphPath getShortestPathImpl(V a, V b) - { - int v_a = vertices.indexOf(a); - int v_b = vertices.indexOf(b); - - List edges = new ArrayList(); - shortestPathRecur(edges, v_a, v_b); - - // no path, return null - if (edges.size() < 1) { - return null; - } - - GraphPathImpl path = - new GraphPathImpl(graph, a, b, edges, edges.size()); - - return path; - } - - /** - * Calculate the shortest paths (not done per default) - */ - private void lazyCalculatePaths() - { - // already we have calculated it once. - if (paths != null) { - return; - } - - lazyCalculateMatrix(); - - Map, GraphPath> sps = - new HashMap, GraphPath>(); - int n = vertices.size(); - - nShortestPaths = 0; - for (int i = 0; i < n; i++) { - V v_i = vertices.get(i); - for (int j = 0; j < n; j++) { - // don't count this. - if (i == j) { - continue; - } - - V v_j = vertices.get(j); - - GraphPath path = getShortestPathImpl(v_i, v_j); - - // we got a path - if (path != null) { - sps.put(new VertexPair(v_i, v_j), path); - nShortestPaths++; - } - } - } - - this.paths = sps; - } - - /** - * Get shortest paths from a vertex to all other vertices in the graph. - * - * @param v the originating vertex - * - * @return List of paths - */ - public List> getShortestPaths(V v) - { - lazyCalculatePaths(); - List> found = new ArrayList>(); - - // TODO: two-level map for paths so that we don't have to - // iterate over all paths here! - for (VertexPair pair : paths.keySet()) { - if (pair.getFirst().equals(v)) { - found.add(paths.get(pair)); - } - } - - return found; - } -} - -// End FloydWarshallShortestPaths.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/HamiltonianCycle.java b/jgrapht-core/src/main/java/org/jgrapht/alg/HamiltonianCycle.java deleted file mode 100644 index 0903f928ddf..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/HamiltonianCycle.java +++ /dev/null @@ -1,120 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * HamiltonianCycle.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 17-Feb-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.graph.*; - - -/** - * This class will deal with finding the optimal or approximately optimal - * minimum tour (hamiltonian cycle) or commonly known as the Traveling - * Salesman Problem. - * - * @author Andrew Newell - */ -public class HamiltonianCycle -{ - //~ Methods ---------------------------------------------------------------- - - /** - * This method will return an approximate minimal traveling salesman tour - * (hamiltonian cycle). This algorithm requires that the graph be complete - * and the triangle inequality exists (if x,y,z are vertices then - * d(x,y)+d(y,z) - * @param - * @param g is the graph to find the optimal tour for. - * - * @return The optimal tour as a list of vertices. - */ - public static List getApproximateOptimalForCompleteGraph( - SimpleWeightedGraph g) - { - List vertices = new LinkedList(g.vertexSet()); - - // If the graph is not complete then return null since this algorithm - // requires the graph be complete - if ((vertices.size() * (vertices.size() - 1) / 2) - != g.edgeSet().size()) - { - return null; - } - - List tour = new LinkedList(); - - // Each iteration a new vertex will be added to the tour until all - // vertices have been added - while (tour.size() != g.vertexSet().size()) { - boolean firstEdge = true; - double minEdgeValue = 0; - int minVertexFound = 0; - int vertexConnectedTo = 0; - - // A check will be made for the shortest edge to a vertex not within - // the tour and that new vertex will be added to the vertex - for (int i = 0; i < tour.size(); i++) { - V v = tour.get(i); - for (int j = 0; j < vertices.size(); j++) { - double weight = - g.getEdgeWeight(g.getEdge(v, vertices.get(j))); - if (firstEdge || (weight < minEdgeValue)) { - firstEdge = false; - minEdgeValue = weight; - minVertexFound = j; - vertexConnectedTo = i; - } - } - } - tour.add(vertexConnectedTo, vertices.get(minVertexFound)); - vertices.remove(minVertexFound); - } - return tour; - } -} - -// End HamiltonianCycle.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPaths.java deleted file mode 100644 index 4e6825128ab..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPaths.java +++ /dev/null @@ -1,258 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPaths.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * The algorithm determines the k shortest simple paths in increasing order of - * weight. Weights can be negative (but no negative cycle is allowed), and paths - * can be constrained by a maximum number of edges. Multigraphs are allowed. - * - *

The algorithm is a variant of the Bellman-Ford algorithm but instead of - * only storing the best path it stores the "k" best paths at each pass, - * yielding a complexity of O(k*n*(m^2)) where m is the number of edges and n is - * the number of vertices. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class KShortestPaths -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Graph on which shortest paths are searched. - */ - private Graph graph; - - private int nMaxHops; - - private int nPaths; - - private V startVertex; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates an object to compute ranking shortest paths between the start - * vertex and others vertices. - * - * @param graph - * @param startVertex - * @param k number of paths to be computed. - */ - public KShortestPaths(Graph graph, V startVertex, int k) - { - this(graph, startVertex, k, graph.vertexSet().size() - 1); - } - - /** - * Creates an object to calculate ranking shortest paths between the start - * vertex and others vertices. - * - * @param graph graph on which shortest paths are searched. - * @param startVertex start vertex of the calculated paths. - * @param nPaths number of ranking paths between the start vertex and an end - * vertex. - * @param nMaxHops maximum number of edges of the calculated paths. - * - * @throws NullPointerException if the specified graph or startVertex is - * null. - * @throws IllegalArgumentException if nPaths is negative or 0. - * @throws IllegalArgumentException if nMaxHops is negative or 0. - */ - public KShortestPaths( - Graph graph, - V startVertex, - int nPaths, - int nMaxHops) - { - assertKShortestPathsFinder(graph, startVertex, nPaths, nMaxHops); - - this.graph = graph; - this.startVertex = startVertex; - this.nPaths = nPaths; - this.nMaxHops = nMaxHops; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the k shortest simple paths in increasing order of weight. - * - * @param endVertex target vertex of the calculated paths. - * - * @return list of paths, or null if no path exists between the - * start vertex and the end vertex. - */ - public List> getPaths(V endVertex) - { - assertGetPaths(endVertex); - - KShortestPathsIterator iter = - new KShortestPathsIterator( - this.graph, - this.startVertex, - endVertex, - this.nPaths); - - // at the i-th pass the shortest paths with less (or equal) than i edges - // are calculated. - for ( - int passNumber = 1; - (passNumber <= this.nMaxHops) - && iter.hasNext(); - passNumber++) - { - iter.next(); - } - - List> list = iter.getPathElements(endVertex); - - if (list == null) { - return null; - } - - List> pathList = new ArrayList>(); - - for (RankingPathElement element : list) { - pathList.add(new PathWrapper(element)); - } - - return pathList; - } - - private void assertGetPaths(V endVertex) - { - if (endVertex == null) { - throw new NullPointerException("endVertex is null"); - } - if (endVertex.equals(this.startVertex)) { - throw new IllegalArgumentException( - "The end vertex is the same as the start vertex!"); - } - if (!this.graph.vertexSet().contains(endVertex)) { - throw new IllegalArgumentException( - "Graph must contain the end vertex!"); - } - } - - private void assertKShortestPathsFinder( - Graph graph, - V startVertex, - int nPaths, - int nMaxHops) - { - if (graph == null) { - throw new NullPointerException("graph is null"); - } - if (startVertex == null) { - throw new NullPointerException("startVertex is null"); - } - if (nPaths <= 0) { - throw new NullPointerException("nPaths is negative or 0"); - } - if (nMaxHops <= 0) { - throw new NullPointerException("nMaxHops is negative or 0"); - } - } - - //~ Inner Classes ---------------------------------------------------------- - - private class PathWrapper - implements GraphPath - { - private RankingPathElement rankingPathElement; - - private List edgeList; - - PathWrapper(RankingPathElement rankingPathElement) - { - this.rankingPathElement = rankingPathElement; - } - - // implement GraphPath - public Graph getGraph() - { - return graph; - } - - // implement GraphPath - public V getStartVertex() - { - return startVertex; - } - - // implement GraphPath - public V getEndVertex() - { - return rankingPathElement.getVertex(); - } - - // implement GraphPath - public List getEdgeList() - { - if (edgeList == null) { - edgeList = rankingPathElement.createEdgeListPath(); - } - return edgeList; - } - - // implement GraphPath - public double getWeight() - { - return rankingPathElement.getWeight(); - } - - // override Object - public String toString() - { - return getEdgeList().toString(); - } - } -} - -// End KShortestPaths.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPathsIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPathsIterator.java deleted file mode 100644 index 6dca6d1317a..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/KShortestPathsIterator.java +++ /dev/null @@ -1,416 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathsIterator.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Helper class for {@link KShortestPaths}. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -class KShortestPathsIterator - implements Iterator> -{ - //~ Instance fields -------------------------------------------------------- - - /** - * End vertex. - */ - private V endVertex; - - /** - * Graph on which shortest paths are searched. - */ - private Graph graph; - - /** - * Number of paths stored at each end vertex. - */ - private int k; - - /** - * Vertices whose ranking shortest paths have been modified during the - * previous pass. - */ - private Set prevImprovedVertices; - - /** - * Stores the paths that improved the vertex in the previous pass. - */ - private Map> prevSeenDataContainer; - - /** - * Stores the vertices that have been seen during iteration and (optionally) - * some additional traversal info regarding each vertex. Key = vertex, value - * = RankingPathElementList list of calculated paths. - */ - private Map> seenDataContainer; - - /** - * Start vertex. - */ - private V startVertex; - - private boolean startVertexEncountered; - - /** - * Stores the number of the path. - */ - private int passNumber = 1; - - //~ Constructors ----------------------------------------------------------- - - /** - * @param graph graph on which shortest paths are searched. - * @param startVertex start vertex of the calculated paths. - * @param endVertex end vertex of the calculated paths. - * @param maxSize number of paths stored at end vertex of the graph. - */ - public KShortestPathsIterator( - Graph graph, - V startVertex, - V endVertex, - int maxSize) - { - assertKShortestPathsIterator(graph, startVertex); - - this.graph = graph; - this.startVertex = startVertex; - this.endVertex = endVertex; - - this.k = maxSize; - - this.seenDataContainer = new HashMap>(); - this.prevSeenDataContainer = - new HashMap>(); - - this.prevImprovedVertices = new HashSet(); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @return true if at least one path has been improved during - * the previous pass, false otherwise. - */ - public boolean hasNext() - { - if (!this.startVertexEncountered) { - encounterStartVertex(); - } - - return !(this.prevImprovedVertices.isEmpty()); - } - - /** - * Returns the list of vertices whose path has been improved during the - * current pass. Complexity = - * - *

    - *
  • O(m*k*(m+n)) where k is the maximum number - * of shortest paths to compute, m is the number of edges of - * the graph and n is the number of vertices of the graph
  • - *
- * - * @see java.util.Iterator#next() - */ - public Set next() - { - if (!this.startVertexEncountered) { - encounterStartVertex(); - } - - // at the i-th pass the shortest paths with i edges are calculated. - if (hasNext()) { - Set improvedVertices = new HashSet(); - - for ( - Iterator iter = this.prevImprovedVertices.iterator(); - iter.hasNext();) - { - V vertex = iter.next(); - if (!vertex.equals(this.endVertex)) { - updateOutgoingVertices(vertex, improvedVertices); - } - } - - savePassData(improvedVertices); - this.passNumber++; - - return improvedVertices; - } - throw new NoSuchElementException(); - } - - /** - * Unsupported. - * - * @see java.util.Iterator#remove() - */ - public void remove() - { - throw new UnsupportedOperationException(); - } - - /** - * Returns the path elements of the ranking shortest paths with less than - * nMaxHops edges between the start vertex and the end vertex. - * - * @param endVertex end vertex. - * - * @return list of RankingPathElement, or null of - * no path exists between the start vertex and the end vertex. - */ - RankingPathElementList getPathElements(V endVertex) - { - return this.seenDataContainer.get(endVertex); - } - - private void assertKShortestPathsIterator(Graph graph, V startVertex) - { - if (graph == null) { - throw new NullPointerException("graph is null"); - } - if (startVertex == null) { - throw new NullPointerException("startVertex is null"); - } - } - - /** - * The first time we see a vertex, make up a new entry for it. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - * - * @return the new entry. - */ - private RankingPathElementList createSeenData(V vertex, E edge) - { - V oppositeVertex = Graphs.getOppositeVertex(this.graph, edge, vertex); - - RankingPathElementList oppositeData = - this.prevSeenDataContainer.get(oppositeVertex); - - // endVertex in argument to ensure that stored paths do not disconnect - // the end-vertex - RankingPathElementList data = - new RankingPathElementList( - this.graph, - this.k, - oppositeData, - edge, - this.endVertex); - - return data; - } - - /** - * Returns an iterator to loop over outgoing edges Edge of the - * vertex. - * - * @param vertex - * - * @return . - */ - private Iterator edgesOfIterator(V vertex) - { - if (this.graph instanceof DirectedGraph) { - return ((DirectedGraph) this.graph).outgoingEdgesOf(vertex) - .iterator(); - } else { - return this.graph.edgesOf(vertex).iterator(); - } - } - - /** - * Initializes the list of paths at the start vertex and adds an empty path. - */ - private void encounterStartVertex() - { - RankingPathElementList data = - new RankingPathElementList( - this.graph, - this.k, - new RankingPathElement( - this.startVertex)); - - this.seenDataContainer.put(this.startVertex, data); - this.prevSeenDataContainer.put(this.startVertex, data); - - // initially the only vertex whose value is considered to have changed - // is the start vertex - this.prevImprovedVertices.add(this.startVertex); - - this.startVertexEncountered = true; - } - - private void savePassData(Set improvedVertices) - { - for (Iterator iter = improvedVertices.iterator(); iter.hasNext();) { - V vertex = iter.next(); - - RankingPathElementList pathElementList = - this.seenDataContainer.get(vertex); - - RankingPathElementList improvedPaths = - new RankingPathElementList( - this.graph, - pathElementList.maxSize, - vertex); - - for ( - Iterator> pathIter = - pathElementList.iterator(); - pathIter.hasNext();) - { - RankingPathElement path = pathIter.next(); - if (path.getHopCount() == this.passNumber) { - // the path has just been computed. - improvedPaths.pathElements.add(path); - } - } - - this.prevSeenDataContainer.put(vertex, improvedPaths); - } - - this.prevImprovedVertices = improvedVertices; - } - - /** - * Try to add the first paths to the specified vertex. These paths reached - * the specified vertex and ended with the specified edge. A new - * intermediary path is stored in the paths list of the specified vertex - * provided that the path can be extended to the end-vertex. - * - * @param vertex vertex reached by a path. - * @param edge edge reaching the vertex. - */ - private boolean tryToAddFirstPaths(V vertex, E edge) - { - // the vertex has not been reached yet - RankingPathElementList data = createSeenData(vertex, edge); - - if (!data.isEmpty()) { - this.seenDataContainer.put(vertex, data); - return true; - } - return false; - } - - /** - * Try to add new paths for the vertex. These new paths reached the - * specified vertex and ended with the specified edge. A new intermediary - * path is stored in the paths list of the specified vertex provided that - * the path can be extended to the end-vertex. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - */ - private boolean tryToAddNewPaths(V vertex, E edge) - { - RankingPathElementList data = this.seenDataContainer.get(vertex); - - V oppositeVertex = Graphs.getOppositeVertex(this.graph, edge, vertex); - RankingPathElementList oppositeData = - this.prevSeenDataContainer.get(oppositeVertex); - - return data.addPathElements(oppositeData, edge); - } - - /** - *

Updates outgoing vertices of the vertex. For each outgoing vertex, the - * new paths are obtained by concatenating the specified edge to the - * calculated paths of the specified vertex. If the weight of a new path is - * greater than the weight of any path stored so far at the outgoing vertex - * then the path is not added, otherwise it is added to the list of paths in - * increasing order of weight.

- * - * Complexity = - * - *
    - *
  • O(d(v)*k*(m+n)) where d(v) is the outgoing - * degree of the specified vertex, k is the maximum number of - * shortest paths to compute, m is the number of edges of the - * graph and n is the number of vertices of the graph
  • - *
- * - * @param vertex - * @param improvedVertices - */ - private void updateOutgoingVertices(V vertex, Set improvedVertices) - { - // try to add new paths for the target vertices of the outgoing edges - // of the vertex in argument. - for (Iterator iter = edgesOfIterator(vertex); iter.hasNext();) { - E edge = iter.next(); - V vertexReachedByEdge = - Graphs.getOppositeVertex(this.graph, edge, - vertex); - - // check if the path does not loop over the start vertex. - if (vertexReachedByEdge != this.startVertex) { - if (this.seenDataContainer.containsKey(vertexReachedByEdge)) { - boolean relaxed = - tryToAddNewPaths(vertexReachedByEdge, - edge); - if (relaxed) { - improvedVertices.add(vertexReachedByEdge); - } - } else { - boolean relaxed = - tryToAddFirstPaths(vertexReachedByEdge, - edge); - if (relaxed) { - improvedVertices.add(vertexReachedByEdge); - } - } - } - } - } -} - -// End KShortestPathsIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/KruskalMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/KruskalMinimumSpanningTree.java deleted file mode 100644 index 574bb15fdf8..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/KruskalMinimumSpanningTree.java +++ /dev/null @@ -1,126 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KruskalMinimumSpanningTree.java - * ------------------------- - * (C) Copyright 2010-2010, by Tom Conerly and Contributors. - * - * Original Author: Tom Conerly - * Contributor(s): - * - * Changes - * ------- - * 02-Feb-2010 : Initial revision (TC); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.alg.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. - * - * @author Tom Conerly - * @since Feb 10, 2010 - */ -public class KruskalMinimumSpanningTree -{ - //~ Instance fields -------------------------------------------------------- - - private double spanningTreeCost; - private Set edgeList; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates and executes a new KruskalMinimumSpanningTree algorithm instance. - * An instance is only good for a single spanning tree; after construction, - * it can be accessed to retrieve information about the spanning tree found. - * - * @param graph the graph to be searched - */ - public KruskalMinimumSpanningTree(final Graph graph) - { - UnionFind forest = new UnionFind(graph.vertexSet()); - ArrayList allEdges = new ArrayList(graph.edgeSet()); - Collections.sort( - allEdges, - new Comparator() { - public int compare(E edge1, E edge2) - { - return Double.valueOf(graph.getEdgeWeight(edge1)).compareTo( - graph.getEdgeWeight(edge2)); - } - }); - - spanningTreeCost = 0; - 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); - } - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the edges making up the tree found. - * - * @return Set of Edges - */ - public Set getEdgeSet() - { - return edgeList; - } - - /** - * Returns the cost of the minimum spanning tree or forest. - * - * @return Cost of the spanning tree - */ - public double getSpanningTreeCost() - { - return spanningTreeCost; - } -} - -// End KruskalMinimumSpanningTree.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/NeighborIndex.java b/jgrapht-core/src/main/java/org/jgrapht/alg/NeighborIndex.java deleted file mode 100644 index 36bfa821549..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/NeighborIndex.java +++ /dev/null @@ -1,262 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * NeighborIndex.java - * -------------------------- - * (C) Copyright 2005-2008, by Charles Fry and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 13-Dec-2005 : Initial revision (CF); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.event.*; -import org.jgrapht.util.*; - - -/** - * 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. - * - *

Edge direction is ignored when evaluating neighbors; to take edge - * direction into account when indexing neighbors, use {@link - * DirectedNeighborIndex}. - * - *

A vertex's neighbors are cached the first time they are asked for (i.e. - * the index is built on demand). The index will only be updated automatically - * if it is added to the associated graph as a listener. If it is added as a - * listener to a graph other than the one it indexes, results are undefined.

- * - * @author Charles Fry - * @since Dec 13, 2005 - */ -public class NeighborIndex - implements GraphListener -{ - //~ Instance fields -------------------------------------------------------- - - Map> neighborMap = new HashMap>(); - private Graph graph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a neighbor index for the specified undirected graph. - * - * @param g the graph for which a neighbor index is to be created. - */ - public NeighborIndex(Graph g) - { - // no need to distinguish directedgraphs as we don't do traversals - graph = g; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the set of vertices which are adjacent to a specified vertex. The - * returned set is backed by the index, and will be updated when the graph - * changes as long as the index has been added as a listener to the graph. - * - * @param v the vertex whose neighbors are desired - * - * @return all unique neighbors of the specified vertex - */ - public Set neighborsOf(V v) - { - return getNeighbors(v).getNeighbors(); - } - - /** - * 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(Object)} 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) - { - return getNeighbors(v).getNeighborList(); - } - - /** - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public void edgeAdded(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - V source = graph.getEdgeSource(edge); - V target = graph.getEdgeTarget(edge); - - // if a map does not already contain an entry, - // then skip addNeighbor, since instantiating the map - // will take care of processing the edge (which has already - // been added) - - if (neighborMap.containsKey(source)) { - getNeighbors(source).addNeighbor(target); - } else { - getNeighbors(source); - } - if (neighborMap.containsKey(target)) { - getNeighbors(target).addNeighbor(source); - } else { - getNeighbors(target); - } - } - - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - V source = e.getEdgeSource(); - V target = e.getEdgeTarget(); - if (neighborMap.containsKey(source)) { - neighborMap.get(source).removeNeighbor(target); - } - if (neighborMap.containsKey(target)) { - neighborMap.get(target).removeNeighbor(source); - } - } - - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ - public void vertexAdded(GraphVertexChangeEvent e) - { - // nothing to cache until there are edges - } - - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ - public void vertexRemoved(GraphVertexChangeEvent e) - { - neighborMap.remove(e.getVertex()); - } - - private Neighbors getNeighbors(V v) - { - Neighbors neighbors = neighborMap.get(v); - if (neighbors == null) { - neighbors = new Neighbors(v, - Graphs.neighborListOf(graph, v)); - neighborMap.put(v, neighbors); - } - return neighbors; - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * 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(V v, 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; - } - } -} - -// End NeighborIndex.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElement.java b/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElement.java deleted file mode 100644 index 0ee3047c951..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElement.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * RankingPathElement.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.*; - - -/** - * Helper class for {@link KShortestPaths}. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -final class RankingPathElement - extends AbstractPathElement -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Weight of the path. - */ - private double weight; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a path element by concatenation of an edge to a path element. - * - * @param pathElement - * @param edge edge reaching the end vertex of the path element created. - * @param weight total cost of the created path element. - */ - RankingPathElement( - Graph graph, - RankingPathElement pathElement, - E edge, - double weight) - { - super(graph, pathElement, edge); - this.weight = weight; - } - - /** - * Creates an empty path element. - * - * @param vertex end vertex of the path element. - */ - RankingPathElement(V vertex) - { - super(vertex); - this.weight = 0; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the weight of the path. - * - * @return . - */ - public double getWeight() - { - return this.weight; - } - - /** - * Returns the previous path element. - * - * @return null is the path is empty. - */ - public RankingPathElement getPrevPathElement() - { - return (RankingPathElement) super.getPrevPathElement(); - } -} - -// End RankingPathElement.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElementList.java b/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElementList.java deleted file mode 100644 index 532fcc7406d..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/RankingPathElementList.java +++ /dev/null @@ -1,436 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * RankingPathElementList.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 05-Jul-2007 : Added support for generics (JVS); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * List of simple paths in increasing order of weight. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -final class RankingPathElementList - extends AbstractPathElementList> -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Vertex that paths of the list must not disconnect. - */ - private V guardVertexToNotDisconnect = null; - - private Map, Boolean> path2disconnect = - new HashMap, Boolean>(); - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a list with an empty path. The list size is 1. - * - * @param maxSize max number of paths the list is able to store. - */ - RankingPathElementList( - Graph graph, - int maxSize, - RankingPathElement pathElement) - { - super(graph, maxSize, pathElement); - } - - /** - * Creates paths obtained by concatenating the specified edge to the - * specified paths. - * - * @param prevPathElementList paths, list of - * RankingPathElement. - * @param edge edge reaching the end vertex of the created paths. - * @param maxSize maximum number of paths the list is able to store. - */ - RankingPathElementList( - Graph graph, - int maxSize, - RankingPathElementList elementList, - E edge) - { - this(graph, maxSize, elementList, edge, null); - - assert (!this.pathElements.isEmpty()); - } - - /** - * Creates paths obtained by concatenating the specified edge to the - * specified paths. - * - * @param prevPathElementList paths, list of - * RankingPathElement. - * @param edge edge reaching the end vertex of the created paths. - * @param maxSize maximum number of paths the list is able to store. - */ - RankingPathElementList( - Graph graph, - int maxSize, - RankingPathElementList elementList, - E edge, - V guardVertexToNotDisconnect) - { - super(graph, maxSize, elementList, edge); - this.guardVertexToNotDisconnect = guardVertexToNotDisconnect; - - // loop over the path elements in increasing order of weight. - for (int i = 0; i < elementList.size(); i++) { - RankingPathElement prevPathElement = elementList.get(i); - - if (isNotValidPath(prevPathElement, edge)) { - continue; - } - - if (size() < this.maxSize) { - double weight = calculatePathWeight(prevPathElement, edge); - RankingPathElement newPathElement = - new RankingPathElement( - this.graph, - prevPathElement, - edge, - weight); - - // the new path is inserted at the end of the list. - this.pathElements.add(newPathElement); - } - } - } - - /** - * Creates an empty list. The list size is 0. - * - * @param maxSize max number of paths the list is able to store. - */ - RankingPathElementList(Graph graph, int maxSize, V vertex) - { - super(graph, maxSize, vertex); - } - - //~ Methods ---------------------------------------------------------------- - - /** - *

Adds paths in the list at vertex y. Candidate paths are obtained by - * concatenating the specified edge (v->y) to the paths - * elementList at vertex v.

- * - * Complexity = - * - *
    - *
  • w/o guard-vertex: O(k*np) where k is the - * max size limit of the list and np is the maximum number of - * vertices in the paths stored in the list
  • - *
  • with guard-vertex: O(k*(m+n)) where k is - * the max size limit of the list, m is the number of edges of - * the graph and n is the number of vertices of the graph, - * O(m+n) being the complexity of the - * ConnectivityInspector to check whether a path exists towards the - * guard-vertex
  • - *
- * - * @param elementList list of paths at vertex v. - * @param edge edge (v->y). - * - * @return true if at least one path has been added in the - * list, false otherwise. - */ - public boolean addPathElements( - RankingPathElementList elementList, - E edge) - { - assert (this.vertex.equals( - Graphs.getOppositeVertex( - this.graph, - edge, - elementList.getVertex()))); - - boolean pathAdded = false; - - // loop over the paths elements of the list at vertex v. - for ( - int vIndex = 0, yIndex = 0; - vIndex < elementList.size(); - vIndex++) - { - RankingPathElement prevPathElement = elementList.get(vIndex); - - if (isNotValidPath(prevPathElement, edge)) { - // checks if path is simple and if guard-vertex is not - // disconnected. - continue; - } - double newPathWeight = calculatePathWeight(prevPathElement, edge); - RankingPathElement newPathElement = - new RankingPathElement( - this.graph, - prevPathElement, - edge, - newPathWeight); - - // loop over the paths of the list at vertex y from yIndex to the - // end. - RankingPathElement yPathElement = null; - for (; yIndex < size(); yIndex++) { - yPathElement = get(yIndex); - - // case when the new path is shorter than the path Py stored at - // index y - if (newPathWeight < yPathElement.getWeight()) { - this.pathElements.add(yIndex, newPathElement); - pathAdded = true; - - // ensures max size limit is not exceeded. - if (size() > this.maxSize) { - this.pathElements.remove(this.maxSize); - } - break; - } - - // case when the new path is of the same length as the path Py - // stored at index y - if (newPathWeight == yPathElement.getWeight()) { - this.pathElements.add(yIndex + 1, newPathElement); - pathAdded = true; - - // ensures max size limit is not exceeded. - if (size() > this.maxSize) { - this.pathElements.remove(this.maxSize); - } - break; - } - } - - // case when the new path is longer than the longest path in the - // list (Py stored at the last index y) - if (newPathWeight > yPathElement.getWeight()) { - // ensures max size limit is not exceeded. - if (size() < this.maxSize) { - // the new path is inserted at the end of the list. - this.pathElements.add(newPathElement); - pathAdded = true; - } else { - // max size limit is reached -> end of the loop over the - // paths elements of the list at vertex v. - break; - } - } - } - - return pathAdded; - } - - /** - * @return list of RankingPathElement. - */ - List> getPathElements() - { - return this.pathElements; - } - - /** - * Costs taken into account are the weights stored in Edge - * objects. - * - * @param pathElement - * @param edge the edge via which the vertex was encountered. - * - * @return the cost obtained by concatenation. - * - * @see Graph#getEdgeWeight(E) - */ - private double calculatePathWeight( - RankingPathElement pathElement, - E edge) - { - double pathWeight = this.graph.getEdgeWeight(edge); - - // otherwise it's the start vertex. - if ((pathElement.getPrevEdge() != null)) { - pathWeight += pathElement.getWeight(); - } - - return pathWeight; - } - - /** - * Ensures that paths of the list do not disconnect the guard-vertex. - * - * @return true if the specified path element disconnects the - * guard-vertex, false otherwise. - */ - private boolean isGuardVertexDisconnected( - RankingPathElement prevPathElement) - { - if (this.guardVertexToNotDisconnect == null) { - return false; - } - - if (this.path2disconnect.containsKey(prevPathElement)) { - return this.path2disconnect.get(prevPathElement); - } - - ConnectivityInspector connectivityInspector; - MaskFunctor connectivityMask; - - if (this.graph instanceof DirectedGraph) { - connectivityMask = new PathMask(prevPathElement); - DirectedMaskSubgraph connectivityGraph = - new DirectedMaskSubgraph( - (DirectedGraph) this.graph, - connectivityMask); - connectivityInspector = - new ConnectivityInspector( - connectivityGraph); - } else { - connectivityMask = new PathMask(prevPathElement); - UndirectedMaskSubgraph connectivityGraph = - new UndirectedMaskSubgraph( - (UndirectedGraph) this.graph, - connectivityMask); - connectivityInspector = - new ConnectivityInspector( - connectivityGraph); - } - - if (connectivityMask.isVertexMasked(this.guardVertexToNotDisconnect)) { - // the guard-vertex was already in the path element -> invalid path - this.path2disconnect.put(prevPathElement, true); - return true; - } - - if (!connectivityInspector.pathExists( - this.vertex, - this.guardVertexToNotDisconnect)) - { - this.path2disconnect.put(prevPathElement, true); - return true; - } - - this.path2disconnect.put(prevPathElement, false); - return false; - } - - private boolean isNotValidPath( - RankingPathElement prevPathElement, - E edge) - { - return !isSimplePath(prevPathElement, edge) - || isGuardVertexDisconnected(prevPathElement); - } - - /** - * Ensures that paths of the list are simple (check that the vertex was not - * already in the path element). - * - * @param prevPathElement - * @param edge - * - * @return true if the resulting path (obtained by - * concatenating the specified edge to the specified path) is simple, - * false otherwise. - */ - private boolean isSimplePath( - RankingPathElement prevPathElement, - E edge) - { - RankingPathElement pathElementToTest = prevPathElement; - while (pathElementToTest.getPrevEdge() != null) { - if (pathElementToTest.getVertex() == this.vertex) { - return false; - } else { - pathElementToTest = pathElementToTest.getPrevPathElement(); - } - } - - return true; - } - - //~ Inner Classes ---------------------------------------------------------- - - private static class PathMask - implements MaskFunctor - { - private Set maskedEdges; - - private Set maskedVertices; - - /** - * Creates a mask for all the edges and the vertices of the path - * (including the 2 extremity vertices). - * - * @param pathElement - */ - PathMask(RankingPathElement pathElement) - { - this.maskedEdges = new HashSet(); - this.maskedVertices = new HashSet(); - - while (pathElement.getPrevEdge() != null) { - this.maskedEdges.add(pathElement.getPrevEdge()); - this.maskedVertices.add(pathElement.getVertex()); - pathElement = pathElement.getPrevPathElement(); - } - this.maskedVertices.add(pathElement.getVertex()); - } - - // implement MaskFunctor - public boolean isEdgeMasked(E edge) - { - return this.maskedEdges.contains(edge); - } - - // implement MaskFunctor - public boolean isVertexMasked(V vertex) - { - return this.maskedVertices.contains(vertex); - } - } -} - -// End RankingPathElementList.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/StoerWagnerMinimumCut.java b/jgrapht-core/src/main/java/org/jgrapht/alg/StoerWagnerMinimumCut.java index 3fecb74fd4c..3c3419d3902 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/StoerWagnerMinimumCut.java +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/StoerWagnerMinimumCut.java @@ -1,192 +1,176 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2011, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * StoerWagnerMinimumCut.java - * ---------------- - * (C) Copyright 2011-2011, by Robby McKilliam and Contributors. +/* + * (C) Copyright 2011-2023, by Robby McKilliam, Ernst de Ridder and Contributors. * - * Original Author: Robby McKilliam - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id: StoerWagnerMinimumCut.java $ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 java.util.*; - import org.jgrapht.*; import org.jgrapht.graph.*; -import org.jgrapht.util.*; +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. + * 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 { - //~ Instance fields -------------------------------------------------------- + final Graph, DefaultWeightedEdge> workingGraph; - final WeightedGraph, DefaultWeightedEdge> workingGraph; - - double bestcutweight = Double.POSITIVE_INFINITY; - Set bestCut; - - boolean firstRun = true; - - //~ Constructors ----------------------------------------------------------- + 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(WeightedGraph graph) + public StoerWagnerMinimumCut(Graph graph) { - //get a version of this graph where each vertex is wrapped with a list - workingGraph = - new SimpleWeightedGraph, DefaultWeightedEdge>( - DefaultWeightedEdge.class); - Map> vertexMap = new HashMap>(); + 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(); + 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); - DefaultWeightedEdge eNew = workingGraph.addEdge(sNew, tNew); - workingGraph.setEdgeWeight(eNew, graph.getEdgeWeight(e)); + + // 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. + // arbitrary vertex used to seed the algorithm. Set a = workingGraph.vertexSet().iterator().next(); - while (workingGraph.vertexSet().size() > 2) { + + while (workingGraph.vertexSet().size() > 1) { minimumCutPhase(a); } } - //~ Methods ---------------------------------------------------------------- - /** - * Implements the MinimumCutPhase function of Stoer and Wagner + * Implements the MinimumCutPhase function of Stoer and Wagner. + * + * @param a the vertex */ protected void minimumCutPhase(Set a) { - //construct sorted queue with vertices connected to vertex a - PriorityQueue queue = - new PriorityQueue(); - Map, VertexAndWeight> dmap = - new HashMap, VertexAndWeight>(); + // 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) { - Double w = - -workingGraph.getEdgeWeight(workingGraph.getEdge(v, a)); - VertexAndWeight vandw = new VertexAndWeight(v, w); - queue.add(vandw); - dmap.put(v, vandw); + 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 iteratatively update the queue to get the required vertex ordering - List> list = - new ArrayList>(workingGraph.vertexSet().size()); - list.add(a); + // Now iteratively update the queue to get the required vertex ordering + while (!queue.isEmpty()) { Set v = queue.poll().vertex; dmap.remove(v); - list.add(v); + + beforelast = last; + last = v; + for (DefaultWeightedEdge e : workingGraph.edgesOf(v)) { - Set vc; - if (v != workingGraph.getEdgeSource(e)) { - vc = workingGraph.getEdgeSource(e); - } else { - vc = workingGraph.getEdgeTarget(e); - } - if (dmap.get(vc) != null) { - Double neww = - -workingGraph.getEdgeWeight(workingGraph.getEdge(v, vc)) - + dmap.get(vc).weight; - queue.remove(dmap.get(vc)); //this is O(logn) but could be - //O(1)? - dmap.get(vc).weight = neww; - queue.add(dmap.get(vc)); //this is O(logn) but could be - //O(1)? + 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)? } } } - //if this is the first run we compute the weight of last vertex in the - //list - if (firstRun) { - Set v = list.get(list.size() - 1); - double w = vertexWeight(v); - if (w < bestcutweight) { - bestcutweight = w; - bestCut = v; - } - firstRun = false; + // Update the best cut + double w = vertexWeight(last); + if (w < bestCutWeight) { + bestCutWeight = w; + bestCut = last; } - //the last two elements in list are the vertices we want to merge. - Set s = list.get(list.size() - 2); - Set t = list.get(list.size() - 1); - - //merge these vertices and get the weight. - VertexAndWeight vw = mergeVertices(s, t); - - //If this is the best cut so far store it. - if (vw.weight < bestcutweight) { - bestcutweight = vw.weight; - bestCut = vw.vertex; - } + // 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 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() { @@ -194,53 +178,54 @@ public Set minCut() } /** - * Merges vertex t into vertex s, summing the weights as required. Returns - * the merged vertex and the sum of its weights + * 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(); - for (V v : s) { - set.add(v); - } - for (V v : t) { - set.add(v); - } + // 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 + // 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); - double wtv = 0.0, wsv = 0.0; if (etv != null) { - wtv = workingGraph.getEdgeWeight(etv); + neww += workingGraph.getEdgeWeight(etv); } if (esv != null) { - wsv = workingGraph.getEdgeWeight(esv); + neww += workingGraph.getEdgeWeight(esv); } - double neww = wtv + wsv; - wsum += neww; - if (neww != 0.0) { - workingGraph.setEdgeWeight( - workingGraph.addEdge(set, v), - neww); + if ((etv != null) || (esv != null)) { + wsum += neww; + workingGraph.setEdgeWeight(workingGraph.addEdge(set, v), neww); } } } - //remove original vertices + // remove original vertices workingGraph.removeVertex(t); workingGraph.removeVertex(s); - return new VertexAndWeight(set, wsum); + 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) { @@ -251,8 +236,6 @@ public double vertexWeight(Set v) return wsum; } - //~ Inner Classes ---------------------------------------------------------- - /** * Class for weighted vertices */ @@ -261,23 +244,47 @@ protected class VertexAndWeight { public Set vertex; public Double weight; + public boolean active; // active == neighbour in A - public VertexAndWeight(Set v, double w) + /** + * 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; } - @Override public int compareTo(VertexAndWeight that) + /** + * compareTo that sorts in reverse order because we need extract-max and queue provides + * extract-min. + */ + @Override + public int compareTo(VertexAndWeight that) { - return Double.compare(weight, that.weight); + 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() + @Override + public String toString() { return "(" + vertex + ", " + weight + ")"; } } } - -// End StoerWagnerMinimumCut.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/StrongConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/StrongConnectivityInspector.java deleted file mode 100644 index 2dd92ed7eb1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/StrongConnectivityInspector.java +++ /dev/null @@ -1,393 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * StrongConnectivityInspector.java - * -------------------------- - * (C) Copyright 2005-2008, by Christian Soltenborn and Contributors. - * - * Original Author: Christian Soltenborn - * - * $Id$ - * - * Changes - * ------- - * 2-Feb-2005 : Initial revision (CS); - * 5-Feb-2007 : fixed NullPointerException (CS); - * 1-Apr-2008 : Reduced memory consumption (CS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - *

Complements the {@link org.jgrapht.alg.ConnectivityInspector} class with - * the capability to compute the strongly connected components of a directed - * graph. The algorithm is implemented after "Cormen et al: Introduction to - * agorithms", Chapter 22.5. It has a running time of O(V + E).

- * - *

Unlike {@link org.jgrapht.alg.ConnectivityInspector}, this class does not - * implement incremental inspection. The full algorithm is executed at the first - * call of {@link StrongConnectivityInspector#stronglyConnectedSets()} or {@link - * StrongConnectivityInspector#isStronglyConnected()}.

- * - * @author Christian Soltenborn - * @author Christian Hammer - * @since Feb 2, 2005 - */ -public class StrongConnectivityInspector -{ - //~ Instance fields -------------------------------------------------------- - - // the graph to compute the strongly connected sets for - private final DirectedGraph graph; - - // stores the vertices, ordered by their finishing time in first dfs - private LinkedList> orderedVertices; - - // the result of the computation, cached for future calls - private List> stronglyConnectedSets; - - // the result of the computation, cached for future calls - private List> stronglyConnectedSubgraphs; - - // maps vertices to their VertexData object - private Map> vertexToVertexData; - - //~ Constructors ----------------------------------------------------------- - - /** - * The constructor of the StrongConnectivityInspector class. - * - * @param directedGraph the graph to inspect - * - * @throws IllegalArgumentException - */ - public StrongConnectivityInspector(DirectedGraph directedGraph) - { - if (directedGraph == null) { - throw new IllegalArgumentException("null not allowed for graph!"); - } - - graph = directedGraph; - vertexToVertexData = null; - orderedVertices = null; - stronglyConnectedSets = null; - stronglyConnectedSubgraphs = null; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the graph inspected by the StrongConnectivityInspector. - * - * @return the graph inspected by this StrongConnectivityInspector - */ - public DirectedGraph getGraph() - { - return graph; - } - - /** - * Returns true if the graph of this - * StronglyConnectivityInspector instance is strongly connected. - * - * @return true if the graph is strongly connected, false otherwise - */ - public boolean isStronglyConnected() - { - return stronglyConnectedSets().size() == 1; - } - - /** - * 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 - */ - public List> stronglyConnectedSets() - { - if (stronglyConnectedSets == null) { - orderedVertices = new LinkedList>(); - stronglyConnectedSets = new Vector>(); - - // 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) - DirectedGraph 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; - } - - /** - *

Computes a list of {@link DirectedSubgraph}s 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.

- * - *

NOTE: Calling this method will first execute {@link - * StrongConnectivityInspector#stronglyConnectedSets()}. If you don't need - * subgraphs, use that method.

- * - * @return a list of subgraphs representing the strongly connected - * components - */ - public List> stronglyConnectedSubgraphs() - { - if (stronglyConnectedSubgraphs == null) { - List> sets = stronglyConnectedSets(); - stronglyConnectedSubgraphs = - new Vector>(sets.size()); - - for (Set set : sets) { - stronglyConnectedSubgraphs.add( - new DirectedSubgraph( - graph, - set, - null)); - } - } - - return stronglyConnectedSubgraphs; - } - - /* - * Creates a VertexData object for every vertex in the graph and stores - * them - * in a HashMap. - */ - private void createVertexData() - { - vertexToVertexData = - new HashMap>(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( - DirectedGraph 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()) { - if (vertices == null) { - orderedVertices.addFirst(data.getFinishedData()); - } - } - } - } - - /* - * Resets all VertexData objects. - */ - private void resetVertexData() - { - for (VertexData data : vertexToVertexData.values()) { - data.setDiscovered(false); - data.setFinished(false); - } - } - - //~ Inner Classes ---------------------------------------------------------- - - /* - * Lightweight class storing some data for every vertex. - */ - private static abstract class VertexData - { - private byte bitfield; - - private VertexData( - boolean discovered, - boolean finished) - { - this.bitfield = 0; - setDiscovered(discovered); - setFinished(finished); - } - - private boolean isDiscovered() - { - if ((bitfield & 1) == 1) { - return true; - } - return false; - } - - private boolean isFinished() - { - if ((bitfield & 2) == 2) { - return true; - } - return false; - } - - 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; - } - - VertexData getFinishedData() - { - return finishedData; - } - - 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; - } - - VertexData getFinishedData() - { - return null; - } - - V getVertex() - { - return vertex; - } - } -} - -// End StrongConnectivityInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveClosure.java b/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveClosure.java index 4f70b13631d..024445f18bc 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveClosure.java +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveClosure.java @@ -1,64 +1,40 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2007-2023, by Vinayak R Borkar and Contributors. * - * 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. - */ -/* ---------------------- - * TransitiveClosure.java - * ---------------------- - * (C) Copyright 2007, by Vinayak R. Borkar. + * JGraphT : a free Java graph-theory library * - * Original Author: Vinayak R. Borkar - * Contributor(s): + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 5-May-2007: Initial revision (VRB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 java.util.*; - +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 - * @since May 5, 2007 */ public class TransitiveClosure { - //~ Static fields/initializers --------------------------------------------- - /** * Singleton instance. */ public static final TransitiveClosure INSTANCE = new TransitiveClosure(); - //~ Constructors ----------------------------------------------------------- - /** * Private Constructor. */ @@ -66,18 +42,18 @@ private TransitiveClosure() { } - //~ Methods ---------------------------------------------------------------- - /** * 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(); + 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 @@ -119,7 +95,7 @@ public void closeSimpleDirectedGraph(SimpleDirectedGraph graph) } /** - * Computes floor(log_2(n)) + 1 + * Computes floor($\log_2 (n)$) $+ 1$ */ private int computeBinaryLog(int n) { @@ -133,6 +109,26 @@ private int computeBinaryLog(int n) return result; } -} -// End TransitiveClosure.java + /** + * 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); + } + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveReduction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveReduction.java new file mode 100644 index 00000000000..61fd38d7a02 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveReduction.java @@ -0,0 +1,184 @@ +/* + * (C) Copyright 2015-2023, 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 i1 = vertices.indexOf(v1); + final int i2 = vertices.indexOf(v2); + + originalMatrix[i1].set(i2); + } + + // 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))); + } + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/VertexCovers.java b/jgrapht-core/src/main/java/org/jgrapht/alg/VertexCovers.java deleted file mode 100644 index 71a524014ce..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/VertexCovers.java +++ /dev/null @@ -1,157 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * VertexCovers.java - * ----------------- - * (C) Copyright 2003-2008, by Linda Buisman and Contributors. - * - * Original Author: Linda Buisman - * Contributor(s): Barak Naveh - * Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 06-Nov-2003 : Initial revision (LB); - * 07-Jun-2005 : Made generic (CH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.alg.util.*; -import org.jgrapht.graph.*; - - -/** - * Algorithms 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 - * - * @author Linda Buisman - * @since Nov 6, 2003 - */ -public abstract class VertexCovers -{ - //~ Methods ---------------------------------------------------------------- - - /** - * 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. - * - *

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. - *

- * - * @param g the graph for which vertex cover approximation is to be found. - * - * @return a set of vertices which is a vertex cover for the specified - * graph. - */ - public static Set find2ApproximationCover(Graph g) - { - // C <-- {} - Set cover = new HashSet(); - - // G'=(V',E') <-- G(V,E) - Subgraph> sg = - new Subgraph>( - g, - 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 = g.getEdgeSource(e); - V v = g.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 cover; // return C - } - - /** - * Finds a greedy approximation for a minimal vertex cover of a specified - * graph. At each iteration, the algorithm picks the vertex with the highest - * degree and adds it to the cover, until all edges are covered. - * - *

The algorithm works on undirected graphs, but can also work on - * directed graphs when their edge-directions are ignored. To ignore edge - * directions you can use {@link org.jgrapht.Graphs#undirectedGraph(Graph)} - * or {@link org.jgrapht.graph.AsUndirectedGraph}.

- * - * @param g the graph for which vertex cover approximation is to be found. - * - * @return a set of vertices which is a vertex cover for the specified - * graph. - */ - public static Set findGreedyCover(UndirectedGraph g) - { - // C <-- {} - Set cover = new HashSet(); - - // G' <-- G - UndirectedGraph sg = new UndirectedSubgraph(g, null, null); - - // compare vertices in descending order of degree - VertexDegreeComparator comp = - new VertexDegreeComparator(sg); - - // while G' != {} - while (sg.edgeSet().size() > 0) { - // v <-- vertex with maximum degree in G' - V v = Collections.max(sg.vertexSet(), comp); - - // C <-- C U {v} - cover.add(v); - - // remove from G' every edge incident on v, and v itself - sg.removeVertex(v); - } - - return cover; - } -} - -// End VertexCovers.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinder.java new file mode 100644 index 00000000000..e3247bdbef3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinder.java @@ -0,0 +1,114 @@ +/* + * (C) Copyright 2005-2023, 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(); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BronKerboschCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BronKerboschCliqueFinder.java new file mode 100644 index 00000000000..c1f137b6791 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/BronKerboschCliqueFinder.java @@ -0,0 +1,163 @@ +/* + * (C) Copyright 2005-2023, 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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinder.java new file mode 100644 index 00000000000..a716753fb7a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinder.java @@ -0,0 +1,171 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition.java new file mode 100644 index 00000000000..bcec9b5b7c2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition.java @@ -0,0 +1,463 @@ +/* + * (C) Copyright 2015-2023, 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 {@code 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 {@code 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 neighborsY = 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 : neighborsY) { + 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) { + neighborsY.add(z); + E fillEdge = graph.getEdgeSupplier().get(); + fillEdges.add(fillEdge); + addToReach(vertexLabels.get(z), z, reach); + } else { + addToReach(j, z, reach); + } + } + } + } + } + + for (V y : neighborsY) { + 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 {@code graph} induced by the given {@code 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinder.java new file mode 100644 index 00000000000..607c746ee21 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinder.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinder.java new file mode 100644 index 00000000000..c7cc75231a2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinder.java @@ -0,0 +1,201 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clique/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/package-info.java new file mode 100644 index 00000000000..8910e9ce1bd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clique/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2017-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Clique related algorithms. + */ +package org.jgrapht.alg.clique; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GirvanNewmanClustering.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GirvanNewmanClustering.java new file mode 100644 index 00000000000..6d734523c64 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GirvanNewmanClustering.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2021-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithm.java new file mode 100644 index 00000000000..fa883883067 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithm.java @@ -0,0 +1,311 @@ +/* + * (C) Copyright 2021-2021, by Antonia Tsiftsi and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import org.jgrapht.Graph; +import org.jgrapht.alg.interfaces.ClusteringAlgorithm; +import org.jgrapht.alg.util.Pair; +import org.jheaps.AddressableHeap; +import org.jheaps.AddressableHeap.Handle; +import org.jheaps.tree.PairingHeap; + +/** + * The Greedy Modularity algorithm. + * + *

+ * The algorithm partitions the vertices of an undirected graph into communities by greedily + * maximizing the modularity + * of possible communities. Greedy modularity maximization begins with each node in its own + * community and repeatedly joins the pair of communities that lead to the largest modularity + * until no further increase in modularity is possible (a maximum). + *

+ * + *

+ * The algorithm is due to Clauset, Newman and Moore. It is described in detail in the following + * paper: + *

    + *
  • Clauset, A., Newman, M. E., & Moore, C. “Finding community structure in very large networks.” + * Physical Review E 70(6), 2004. + *
  • + *
+ *

+ * + * @author Antonia Tsiftsi + * + * @param the graph vertex type + * @param the graph edge type + */ +public class GreedyModularityAlgorithm + implements + ClusteringAlgorithm +{ + private final Graph graph; + + /** + * Create a new clustering algorithm. + * + * @param graph the graph + */ + public GreedyModularityAlgorithm(Graph graph) + { + this.graph = graph; + } + + @Override + public ClusteringAlgorithm.Clustering getClustering() + { + // create a map for communities where the merge will take place + Map> communitiesMap = new HashMap<>(); + for (V v : graph.iterables().vertices()) { + Set set = new HashSet<>(); + set.add(v); + communitiesMap.put(v, set); + } + + // 1: Map of DQs + int m = graph.edgeSet().size(); + Map> dQ = new HashMap<>(); + for (E e : graph.edgeSet()) { + V vi = graph.getEdgeSource(e); + V vj = graph.getEdgeTarget(e); + + if (vi.equals(vj)) { + // ignore self-loops + continue; + } + + int ki = graph.degreeOf(vi); + int kj = graph.degreeOf(vj); + + // calculation of dQ + double dq = (1.0 / (m)) - ((ki * kj) / (2.0 * m * m)); + + Map columnsI = dQ.get(vi); + if (columnsI == null) { + columnsI = new TreeMap<>(); + dQ.put(vi, columnsI); + } + columnsI.put(vj, dq); + + Map columnsJ = dQ.get(vj); + if (columnsJ == null) { + columnsJ = new TreeMap<>(); + dQ.put(vj, columnsJ); + } + columnsJ.put(vi, dq); + } + + // 1: Pairing Heap of DQs + Map>> dQHeap = new HashMap<>(); + Map>>> dQHeapHandles = new HashMap<>(); + + // 2: Pairing Heap - max dQ of each row + AddressableHeap> maxHeap = new PairingHeap<>(Comparator.reverseOrder()); + Map>> maxHeapHandles = new HashMap<>(); + + // Initialization of heaps + for (V vi : dQ.keySet()) { + AddressableHeap> heap = new PairingHeap<>(Comparator.reverseOrder()); + dQHeap.put(vi, heap); + + Map>> heapHandles = new HashMap<>(); + dQHeapHandles.put(vi, heapHandles); + + Map columns = dQ.get(vi); + for (Entry e : columns.entrySet()) { + heapHandles.put(e.getKey(), heap.insert(e.getValue(), new Pair<>(vi, e.getKey()))); + } + + Handle> viMax = heap.findMin(); + maxHeapHandles.put(vi, maxHeap.insert(viMax.getKey(), viMax.getValue())); + } + + // 3: Map of a + Map a = new HashMap<>(); + Map b = new HashMap<>(); + if (graph.getType().isDirected()) { + for (V v : graph.vertexSet()) { + a.put(v, (double) graph.outDegreeOf(v) / (m)); + b.put(v, (double) graph.inDegreeOf(v) / (m)); + } + } else { + for (V v : graph.vertexSet()) { + a.put(v, (double) graph.degreeOf(v) / (2.0 * m)); + } + b = a; + } + + while (dQ.size() != 1) { + for (V v : dQ.keySet()) { + if (dQHeap.get(v).isEmpty()) { + continue; + } + Handle> minV = dQHeap.get(v).findMin(); + Handle> handle = maxHeapHandles.get(v); + assert minV.getValue().equals(handle.getValue()); + } + + // compute best two communities to merge + AddressableHeap.Handle> max = maxHeap.findMin(); + + // stop if we cannot increase modularity + if (max.getKey() <= 0.0) { + break; + } + + V i = max.getValue().getFirst(); + V j = max.getValue().getSecond(); + + Set nbrsI = dQ.get(i).keySet(); + Set nbrsJ = dQ.get(j).keySet(); + + Set allNbrs = new HashSet<>(nbrsI); + allNbrs.addAll(nbrsJ); + allNbrs.remove(i); + allNbrs.remove(j); + + Set bothNbrs = new HashSet<>(nbrsI); + bothNbrs.retainAll(nbrsJ); + + // update dQ + for (V k : allNbrs) { + double newDQjk; + if (bothNbrs.contains(k)) { // k community connected to both i and j + double dQik = dQ.get(i).get(k); + double dQjk = dQ.get(j).get(k); + newDQjk = dQik + dQjk; + } else if (nbrsI.contains(k)) { // k community connected only to i + double dQik = dQ.get(i).get(k); + newDQjk = dQik - (a.get(j) * b.get(k) + a.get(k) * b.get(j)); + } else { // k community connected only to j + double dQjk = dQ.get(j).get(k); + newDQjk = dQjk - (a.get(i) * b.get(k) + a.get(k) * b.get(i)); + } + // update j in dQ + dQ.get(j).put(k, newDQjk); + + // update k in dQ + dQ.get(k).put(j, newDQjk); + + // update j-th key in k-th heap + Handle> jHandle = dQHeapHandles.get(k).get(j); + if (jHandle == null) { + Handle> newjHandle = + dQHeap.get(k).insert(newDQjk, Pair.of(k, j)); + dQHeapHandles.get(k).put(j, newjHandle); + if (dQHeap.get(k).findMin() == newjHandle) { + maxHeapHandles.get(k).delete(); + maxHeapHandles.put(k, maxHeap.insert(newDQjk, Pair.of(k, j))); + } + } else { + boolean isJMax = dQHeap.get(k).findMin() == jHandle; + jHandle.delete(); + Handle> newjHandle = + dQHeap.get(k).insert(newDQjk, Pair.of(k, j)); + dQHeapHandles.get(k).put(j, newjHandle); + if (dQHeap.get(k).findMin() == newjHandle) { + maxHeapHandles.get(k).delete(); + maxHeapHandles.put(k, maxHeap.insert(newDQjk, Pair.of(k, j))); + } else if (isJMax) { + maxHeapHandles.get(k).delete(); + Handle> newMax = dQHeap.get(k).findMin(); + maxHeapHandles.put(k, maxHeap.insert(newMax.getKey(), newMax.getValue())); + } + } + + // also remove i from k-th row in dQHeap + Handle> iHandle = dQHeapHandles.get(k).get(i); + if (iHandle != null) { + Handle> previousMaxK = maxHeapHandles.get(k); + boolean isIMax = previousMaxK.getValue().equals(iHandle.getValue()); + iHandle.delete(); // remove i from k-th row in dQHeap + dQHeapHandles.get(k).remove(i); + + if (isIMax) { // if i was max, delete and add new max in maxHeapH + previousMaxK.delete(); // remove max in maxHeapH + Handle> newMaxK = dQHeap.get(k).findMin(); + // add new max in maxHeapH + maxHeapHandles.put(k, maxHeap.insert(newMaxK.getKey(), newMaxK.getValue())); + } + } + } + + // clear i row and column in dQ + for (V v : dQ.keySet()) { + dQ.get(v).remove(i); + } + dQ.remove(i); + + // remove heap for i-th row + dQHeap.remove(i); + dQHeapHandles.remove(i); + // remove maxHeapH for i-th row + maxHeapHandles.get(i).delete(); + maxHeapHandles.remove(i); + + // compute new heap for j-th row in dQHeap + AddressableHeap> newJHeap = + new PairingHeap<>(Comparator.reverseOrder()); + Map>> newJHeapHandles = new HashMap<>(); + for (Entry e : dQ.get(j).entrySet()) { + newJHeapHandles + .put(e.getKey(), newJHeap.insert(e.getValue(), new Pair<>(j, e.getKey()))); + } + dQHeap.put(j, newJHeap); + dQHeapHandles.put(j, newJHeapHandles); + + // update maxHeapH for j-th row + if (!newJHeap.isEmpty()) { + Handle> newMaxDQj = newJHeap.findMin(); + maxHeapHandles.get(j).delete(); + maxHeapHandles.put(j, maxHeap.insert(newMaxDQj.getKey(), newMaxDQj.getValue())); + } else { + maxHeapHandles.get(j).delete(); + maxHeapHandles.remove(j); + } + + // update communities by combining community i and community j + communitiesMap.get(j).addAll(communitiesMap.get(i)); + communitiesMap.remove(i); + + // update a + a.put(j, a.get(i) + a.get(j)); + a.put(i, 0d); + if (graph.getType().isDirected()) { + b.put(j, b.get(j) + b.get(i)); + b.put(i, 0d); + } + } + + // update communities list + List> result = new ArrayList<>(); + for (Set set : communitiesMap.values()) { + result.add(set); + } + return new ClusteringAlgorithm.ClusteringImpl<>(result); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/KSpanningTreeClustering.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/KSpanningTreeClustering.java new file mode 100644 index 00000000000..c665efb141a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/KSpanningTreeClustering.java @@ -0,0 +1,112 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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())); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LabelPropagationClustering.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LabelPropagationClustering.java new file mode 100644 index 00000000000..8bcde4c2d8f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/LabelPropagationClustering.java @@ -0,0 +1,349 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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()); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithm.java new file mode 100644 index 00000000000..6471b3edf7d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithm.java @@ -0,0 +1,126 @@ +/* + * (C) Copyright 2021-2021, by Antonia Tsiftsi and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.ArrayList; +import java.util.HashSet; +import java.util.List; +import static java.util.Objects.isNull; +import java.util.Set; +import org.jgrapht.Graph; +import org.jgrapht.GraphTests; +import org.jgrapht.alg.interfaces.ClusteringAlgorithm; + +/** + * A naive implementation of greedy modularity maximization for community detection. + * + *

+ * The algorithm partitions the vertices of an undirected graph into communities by greedily + * maximizing the modularity + * of possible communities. Greedy modularity maximization begins with each node in its own + * community and repeatedly joins the pair of communities that lead to the largest modularity + * until no further increase in modularity is possible (a maximum). + *

+ * + *

+ * This implementation is simple but computationally expensive, with a worst-case complexity + * of O(n^4). It is intended as an easy-to-understand reference implementation rather + * than a performance-optimized solution. + *

+ * + * @author Antonia Tsiftsi + * + * @param the graph vertex type + * @param the graph edge type + */ +public class NaiveGreedyModularityAlgorithm + implements + ClusteringAlgorithm +{ + private final Graph graph; + + /** + * Create a new Naive Greedy clustering algorithm. + * + * @param graph the graph + */ + public NaiveGreedyModularityAlgorithm(Graph graph) + { + this.graph = GraphTests.requireUndirected(graph); + } + + @Override + public ClusteringAlgorithm.Clustering getClustering() + { + int i, j; + + // create one community for each node + List> communities = new ArrayList<>(); + for (V v : graph.iterables().vertices()) { + Set set = Set.of(v); + communities.add(set); + } + + Double oldModularity = null; + UndirectedModularityMeasurer measurer = new UndirectedModularityMeasurer<>(graph); + double curModularity = measurer.modularity(communities); + + // greedily merge communities until no improvement is possible + while (isNull(oldModularity) || oldModularity < curModularity) { + oldModularity = curModularity; + List> bestCommunities = null; + + for (i = 0; i < communities.size(); i++) { + for (j = 0; j < communities.size(); j++) { + if (j <= i) { + continue; + } + + // initialize trialCommunities + List> trialCommunities = new ArrayList<>(); + for (int k = 0; k < communities.size(); k++) { + if (k != j && k != i) { + trialCommunities.add(communities.get(k)); + } + } + + // create trial partition + Set merge = new HashSet<>(); + merge.addAll(communities.get(i)); + merge.addAll(communities.get(j)); + trialCommunities.add(merge); + + // check the modularity of the trial partition + double trialModularity = measurer.modularity(trialCommunities); + if (trialModularity >= curModularity) { + curModularity = trialModularity; + bestCommunities = trialCommunities; + } + } + } + if (bestCommunities == null) { + break; + } + communities = bestCommunities; + } + + ClusteringAlgorithm.ClusteringImpl clustering = + new ClusteringAlgorithm.ClusteringImpl<>(communities); + return clustering; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurer.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurer.java new file mode 100644 index 00000000000..7c75789c61d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurer.java @@ -0,0 +1,153 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jgrapht.Graph; +import org.jgrapht.GraphTests; +import org.jgrapht.Graphs; + +/** + * A modularity measurer. + * + *

+ * This is a utility class which computes the modularity function. It takes as input a list of + * vertex classes $C$ and a graph $G$ and calculates: $Q = \frac{1}{2m} \sum_{ij} \left( A_{ij} - + * \frac{k_i k_j}{2m} \right) \delta(C_i, C_j)$. Here $m$ is the total number of edges and $k_i$ is + * the degree of vertex $i$. $A_{ij}$ is either $1$ or $0$ depending on whether edge $(i,j)$ belongs + * to the graph and $\delta(C_i, C_j)$ is 1 if vertices $i$ and $j$ belong to the same class, $0$ + * otherwise. + * + * @author Dimitrios Michail + * + * @param the vertex type + * @param the edge type + */ +public class UndirectedModularityMeasurer +{ + + private static final String INVALID_PARTITION_OF_VERTICES = "Invalid partition of vertices"; + private final Graph graph; + private double m; + private Map degrees; + + /** + * Construct a new measurer + * + * @param graph the input graph + */ + public UndirectedModularityMeasurer(Graph graph) + { + this.graph = GraphTests.requireUndirected(graph); + this.degrees = new HashMap<>(); + precomputeDegrees(graph); + } + + /** + * Compute the modularity of a vertex partition. + * + * @param partitions the partitions + * @return the modularity + */ + public double modularity(List> partitions) + { + // index partitions and count total (weighted) degree inside each partition + int totalPartitions = partitions.size(); + Map vertexPartition = new HashMap<>(); + double[] weightedDegreeInPartition = new double[totalPartitions]; + int curPartition = 0; + for (Set partition : partitions) { + weightedDegreeInPartition[curPartition] = 0d; + for (V v : partition) { + vertexPartition.put(v, curPartition); + Double d = degrees.get(v); + if (d == null) { + throw new IllegalArgumentException(INVALID_PARTITION_OF_VERTICES); + } + weightedDegreeInPartition[curPartition] += d; + } + curPartition++; + } + + // count (weighted) edges inside each partition + double[] edgeWeightInPartition = new double[totalPartitions]; + for (E e : graph.edgeSet()) { + V v = graph.getEdgeSource(e); + V u = graph.getEdgeTarget(e); + Integer pv = vertexPartition.get(v); + if (pv == null) { + throw new IllegalArgumentException(INVALID_PARTITION_OF_VERTICES); + } + Integer pu = vertexPartition.get(u); + if (pu == null) { + throw new IllegalArgumentException(INVALID_PARTITION_OF_VERTICES); + } + if (pv.intValue() == pu.intValue()) { + // same partition + edgeWeightInPartition[pv] += graph.getEdgeWeight(e); + } + } + + // compute modularity summing over partitions + double mod = 0d; + for (int p = 0; p < totalPartitions; p++) { + double expectedEdgeWeightInPartition = + weightedDegreeInPartition[p] * weightedDegreeInPartition[p] / (2d * m); + mod += 2d * edgeWeightInPartition[p] - expectedEdgeWeightInPartition; + } + mod /= 2d * m; + + return mod; + } + + /** + * Pre-compute vertex (weighted) degrees. + * + * @param graph the input graph + */ + private void precomputeDegrees(Graph graph) + { + if (graph.getType().isWeighted()) { + m = graph.edgeSet().stream().collect(Collectors.summingDouble(graph::getEdgeWeight)); + for (V v : graph.vertexSet()) { + double sum = 0d; + for (E e : graph.outgoingEdgesOf(v)) { + V u = Graphs.getOppositeVertex(graph, e, v); + if (u.equals(v)) { + sum += 2d * graph.getEdgeWeight(e); + } else { + sum += graph.getEdgeWeight(e); + } + } + degrees.put(v, sum); + } + } else { + m = graph.edgeSet().size(); + for (V v : graph.vertexSet()) { + // degreeof counts loops twice anyway + degrees.put(v, Double.valueOf(graph.degreeOf(v))); + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/package-info.java new file mode 100644 index 00000000000..34d875b1f8b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph clustering algorithms. + */ +package org.jgrapht.alg.clustering; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/BrownBacktrackColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/BrownBacktrackColoring.java new file mode 100644 index 00000000000..9b2c3220216 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/BrownBacktrackColoring.java @@ -0,0 +1,165 @@ +/* + * (C) Copyright 2010-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/ChordalGraphColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/ChordalGraphColoring.java new file mode 100644 index 00000000000..ded4b390322 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/ChordalGraphColoring.java @@ -0,0 +1,187 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/ColorRefinementAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/ColorRefinementAlgorithm.java new file mode 100644 index 00000000000..c7fbb788e96 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/ColorRefinementAlgorithm.java @@ -0,0 +1,375 @@ +/* + * (C) Copyright 2018-2023, 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 + * {@code 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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/GreedyColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/GreedyColoring.java new file mode 100644 index 00000000000..dca941e69ab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/GreedyColoring.java @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/LargestDegreeFirstColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/LargestDegreeFirstColoring.java new file mode 100644 index 00000000000..4675c07ec3d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/LargestDegreeFirstColoring.java @@ -0,0 +1,108 @@ +/* + * (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; + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/RandomGreedyColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/RandomGreedyColoring.java new file mode 100644 index 00000000000..edd10717fce --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/RandomGreedyColoring.java @@ -0,0 +1,71 @@ +/* + * (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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/SaturationDegreeColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/SaturationDegreeColoring.java new file mode 100644 index 00000000000..d5e674869de --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/SaturationDegreeColoring.java @@ -0,0 +1,327 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/SmallestDegreeLastColoring.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/SmallestDegreeLastColoring.java new file mode 100644 index 00000000000..d72760e2f87 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/SmallestDegreeLastColoring.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2010-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/package-info.java new file mode 100644 index 00000000000..85cbae4064a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/package-info.java @@ -0,0 +1,23 @@ +/* + * (C) Copyright 2017-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + + +/** + * Graph coloring algorithms. + */ +package org.jgrapht.alg.color; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/AbstractStrongConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/AbstractStrongConnectivityInspector.java new file mode 100644 index 00000000000..55bbd4f3a27 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/AbstractStrongConnectivityInspector.java @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2005-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BiconnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BiconnectivityInspector.java new file mode 100644 index 00000000000..96e931e619b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BiconnectivityInspector.java @@ -0,0 +1,339 @@ +/* + * (C) Copyright 2017-2023, 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 {@code 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BlockCutpointGraph.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BlockCutpointGraph.java new file mode 100644 index 00000000000..b6f73f8f105 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/BlockCutpointGraph.java @@ -0,0 +1,140 @@ +/* + * (C) Copyright 2007-2023, 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 {@code true} if the vertex is a cutpoint, {@code false} otherwise. + * + * @param vertex vertex in the initial graph. + * @return {@code true} if the vertex is a cutpoint, {@code false} otherwise. + */ + public boolean isCutpoint(V vertex) + { + return cutpoints.contains(vertex); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/ConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/ConnectivityInspector.java new file mode 100644 index 00000000000..03919a03ba0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/ConnectivityInspector.java @@ -0,0 +1,271 @@ +/* + * (C) Copyright 2003-2023, 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 {@code 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 {@code 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 {@code 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 {@code 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/GabowStrongConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/GabowStrongConnectivityInspector.java new file mode 100644 index 00000000000..3eb26f573b4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/GabowStrongConnectivityInspector.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2013-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/KosarajuStrongConnectivityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/KosarajuStrongConnectivityInspector.java new file mode 100644 index 00000000000..0b596a1679e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/KosarajuStrongConnectivityInspector.java @@ -0,0 +1,261 @@ +/* + * (C) Copyright 2005-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivity.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivity.java new file mode 100644 index 00000000000..6643ca14161 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivity.java @@ -0,0 +1,638 @@ +/* + * (C) Copyright 2020-2023, 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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/package-info.java new file mode 100644 index 00000000000..aa54f93a0bb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/package-info.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * 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; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AbstractFundamentalCycleBasis.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AbstractFundamentalCycleBasis.java new file mode 100644 index 00000000000..4e48a23e58e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AbstractFundamentalCycleBasis.java @@ -0,0 +1,173 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentation.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentation.java new file mode 100644 index 00000000000..bae289e7469 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentation.java @@ -0,0 +1,465 @@ +/* + * (C) Copyright 2018-2023, 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code path} is dominated by some path in {@code 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 {@code 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) { + throw new RuntimeException(e); + } + } + } + + /** + * 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/BergeGraphInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/BergeGraphInspector.java new file mode 100644 index 00000000000..b2820ed334a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/BergeGraphInspector.java @@ -0,0 +1,1290 @@ +/* + * (C) Copyright 2016-2023, 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 pathS A Path in g + * @param pathT 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 pathS, GraphPath pathT, 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) || pathS == null || pathT == null) + return null; + if (pathS.getVertexList().stream().anyMatch( + t -> g.containsEdge(t, b2) || g.containsEdge(t, b3) || g.containsEdge(t, s2) + || g.containsEdge(t, s3)) + || pathT.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(pathS, pathT); + if (intersection.size() != 1 || !intersection.contains(m)) + return null; + if (pathS.getVertexList().stream().anyMatch( + s -> s != m && pathT + .getVertexList().stream().anyMatch(t -> t != m && g.containsEdge(s, t)))) + return null; + List edgeList = new LinkedList<>(); + edgeList.addAll(pathT.getEdgeList()); + edgeList.addAll(pathS.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 setM = new HashSet<>(); + setM.addAll(g.vertexSet()); + setM.remove(b1); + setM.remove(b2); + setM.remove(b3); + setM.remove(s1); + setM.remove(s2); + setM.remove(s3); + + Map> mapS1 = new HashMap<>(), + mapS2 = new HashMap<>(), mapS3 = new HashMap<>(), + mapT1 = new HashMap<>(), mapT2 = new HashMap<>(), + mapT3 = new HashMap<>(); + + // find paths which could be the edges of the pyramid + for (V m1 : setM) { + Set validInterior = new HashSet<>(); + validInterior.addAll(setM); + 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); + mapS1.put( + m1, new DijkstraShortestPath<>(subg).getPath(m1, s1)); + validInterior.remove(s1); + validInterior.add(b1); + subg = new AsSubgraph<>(g, validInterior); + mapT1.put( + m1, new DijkstraShortestPath<>(subg).getPath(b1, m1)); + + } + for (V m2 : setM) { + Set validInterior = new HashSet<>(); + validInterior.addAll(setM); + 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); + mapS2.put( + m2, new DijkstraShortestPath<>(subg).getPath(m2, s2)); + validInterior.remove(s2); + validInterior.add(b2); + subg = new AsSubgraph<>(g, validInterior); + mapT2.put( + m2, new DijkstraShortestPath<>(subg).getPath(b2, m2)); + + } + for (V m3 : setM) { + Set validInterior = new HashSet<>(); + validInterior.addAll(setM); + 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); + mapS3.put( + m3, new DijkstraShortestPath<>(subg).getPath(m3, s3)); + validInterior.remove(s3); + validInterior.add(b3); + subg = new AsSubgraph<>(g, validInterior, null); + mapT3.put( + m3, new DijkstraShortestPath<>(subg).getPath(b3, m3)); + } + + // Check if all edges of a pyramid are valid + Set setM1 = new HashSet<>(); + setM1.addAll(setM); + setM1.add(b1); + for (V m1 : setM1) { + GraphPath pathP1 = p( + g, mapS1.get(m1), mapT1.get(m1), m1, b1, b2, b3, s1, s2, + s3); + if (pathP1 == null) + continue; + Set setM2 = new HashSet<>(); + setM2.addAll(setM); + setM2.add(b2); + for (V m2 : setM) { + GraphPath pathP2 = p( + g, mapS2.get(m2), mapT2.get(m2), m2, b2, b1, b3, s2, + s1, s3); + if (pathP2 == null) + continue; + Set setM3 = new HashSet<>(); + setM3.addAll(setM); + setM3.add(b3); + for (V m3 : setM3) { + GraphPath pathP3 = p( + g, mapS3.get(m3), mapT3.get(m3), m3, b3, b1, b2, + s3, s1, s2); + if (pathP3 == null) + continue; + if (certify) { + if ((pathP1.getLength() + pathP2.getLength()) + % 2 == 0) + { + Set set = new HashSet<>(); + set.addAll(pathP1.getVertexList()); + set.addAll(pathP2.getVertexList()); + set.add(aCandidate); + bfOddHoleCertificate( + new AsSubgraph<>(g, set)); + } else if ((pathP1.getLength() + + pathP3.getLength()) % 2 == 0) + { + Set set = new HashSet<>(); + set.addAll(pathP1.getVertexList()); + set.addAll(pathP3.getVertexList()); + set.add(aCandidate); + bfOddHoleCertificate( + new AsSubgraph<>(g, set)); + } else { + Set set = new HashSet<>(); + set.addAll(pathP3.getVertexList()); + set.addAll(pathP2.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 setF = 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; + setF.add(f); + } + + List> componentsOfF = findAllComponents(g, setF); + + Set setX1 = 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; + setX1.add(x1); + } + Set setX2 = 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; + setX2.add(x2); + } + + for (V v1 : setX1) { + if (g.containsEdge(v1, v3)) + continue; + for (V v4 : setX2) { + 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 setY = new HashSet<>(); + for (V y : g.vertexSet()) { + if (isYXComplete(g, y, temp)) { + setY.add(y); + } + } + List> anticomponentsOfY = findAllAnticomponentsOfY(g, setY); + for (Set setX : anticomponentsOfY) { + Set v2v3 = new HashSet<>(); + v2v3.addAll(g.vertexSet()); + v2v3.remove(v2); + v2v3.remove(v3); + v2v3.removeAll(setX); + if (!v2v3.contains(v1) || !v2v3.contains(v4)) + continue; + + GraphPath path = + new DijkstraShortestPath<>(new AsSubgraph<>(g, v2v3)) + .getPath(v1, v4); + if (path == null) + continue; + List listP = path.getVertexList(); + if (!listP.contains(v1) || !listP.contains(v4)) + continue; + + boolean cont = true; + for (V p : listP) { + if (p != v1 && p != v4 && (g.containsEdge(p, v2) + || g.containsEdge(p, v3) || isYXComplete(g, p, setX))) + { + 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 = setX.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 setX 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 setX, V v1, V v2, V v5) + { + Set fPrime = new ConnectivityInspector<>(g).connectedSetOf(v5); + fPrime.removeIf( + t -> t != v5 && isYXComplete(g, t, setX) || 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 setX A set of vertices + * @return whether v has a nonneighbour in X + */ + private boolean hasANonneighbourInX(Graph g, V v, Set setX) + { + return setX.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 setY = new HashSet<>(); + for (V y : g.vertexSet()) { + if (isYXComplete(g, y, triple)) { + setY.add(y); + } + } + List> anticomponents = findAllAnticomponentsOfY(g, setY); + for (Set setX : anticomponents) { + Set fPrime = findMaximalConnectedSubset(g, setX, v1, v2, v5); + Set setF = new HashSet<>(); + setF.addAll(fPrime); + for (V x : setX) { + if (!g.containsEdge(x, v1) && !g.containsEdge(x, v2) + && !g.containsEdge(x, v5) && hasANeighbour(g, fPrime, x)) + setF.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, setF, v4) || !hasANonneighbourInX(g, v4, setX) + || isYXComplete(g, v4, setX)) + 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, setX) + || isYXComplete(g, v3, setX)) + continue; + for (V v6 : setF) { + 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, setX)) + 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 path = + new DijkstraShortestPath<>(g).getPath(v6, v5); + edgeList.addAll(path.getEdgeList()); + if (path.getLength() % 2 == 1) { + V x = setX.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 {@code computeCertificate} is {@code 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 {@code 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 + * {@code 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChinesePostman.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChinesePostman.java new file mode 100644 index 00000000000..abf3bf50b31 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChinesePostman.java @@ -0,0 +1,307 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinder.java new file mode 100644 index 00000000000..da8e4985953 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinder.java @@ -0,0 +1,193 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalityInspector.java new file mode 100644 index 00000000000..5b1a8d77d1c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalityInspector.java @@ -0,0 +1,412 @@ +/* + * (C) Copyright 2018-2023, 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, + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/CycleDetector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/CycleDetector.java new file mode 100644 index 00000000000..f1a59748f42 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/CycleDetector.java @@ -0,0 +1,239 @@ +/* + * (C) Copyright 2004-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/Cycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/Cycles.java new file mode 100644 index 00000000000..0b800e191d4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/Cycles.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/DirectedSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/DirectedSimpleCycles.java new file mode 100644 index 00000000000..9e95f7d06d4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/DirectedSimpleCycles.java @@ -0,0 +1,52 @@ +/* + * (C) Copyright 2013-2023, 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.*; +import java.util.function.Consumer; + +/** + * 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 {@code null}. + */ + default List> findSimpleCycles() + { + List> result = new ArrayList<>(); + findSimpleCycles(result::add); + return result; + } + + /** + * Find the simple cycles of the graph. + * + * @param consumer Consumer that will be called with each cycle found. + */ + void findSimpleCycles(Consumer> consumer); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HawickJamesSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HawickJamesSimpleCycles.java new file mode 100644 index 00000000000..2b1288e663b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HawickJamesSimpleCycles.java @@ -0,0 +1,315 @@ +/* + * (C) Copyright 2014-2023, 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 java.util.function.Consumer; + +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; + + // 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 {@code 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[] listAk = new ArrayList[nVertices]; + for (int j = 0; j < nVertices; j++) { + V v = iToV[j]; + List s = Graphs.successorListOf(graph, v); + listAk[j] = new ArrayList(s.size()); + + for (V value : s) { + listAk[j].add(vToI.get(value)); + } + } + + return listAk; + } + + 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 void findSimpleCycles(Consumer> consumer) + throws IllegalArgumentException + { + if (graph == null) { + throw new IllegalArgumentException("Null graph."); + } + + initState(); + operation = () -> { + List cycle = stack.stream().map(v -> iToV[v]).collect(toList()); + Collections.reverse(cycle); + consumer.accept(cycle); + }; + analyzeCircuits(); + clearState(); + } + + /** + * 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HierholzerEulerianCycle.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HierholzerEulerianCycle.java new file mode 100644 index 00000000000..8487e5cd4e6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HierholzerEulerianCycle.java @@ -0,0 +1,594 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycle.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycle.java new file mode 100644 index 00000000000..8b848dc5fcd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycle.java @@ -0,0 +1,419 @@ +/* + * (C) Copyright 2020-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/JohnsonSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/JohnsonSimpleCycles.java new file mode 100644 index 00000000000..9ce232a6337 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/JohnsonSimpleCycles.java @@ -0,0 +1,339 @@ +/* + * (C) Copyright 2013-2023, 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.*; +import java.util.function.Consumer; + +/** + * 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 Consumer> cycleConsumer = 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> foundSCCs = 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 {@code 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 void findSimpleCycles(Consumer> consumer) + { + if (graph == null) { + throw new IllegalArgumentException("Null graph."); + } + initState(consumer); + + 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; + } + } + + clearState(); + } + + 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> foundSCCs = findSCCS(startIndex); + + // find the SCC with the minimum index + int minIndexFound = Integer.MAX_VALUE; + Set minSCC = null; + for (Set scc : foundSCCs) { + 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 = foundSCCs; + foundSCCs = 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)) { + foundSCCs.add(result); + } + } else { + foundSCCs.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); + cycleConsumer.accept(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(Consumer> consumer) + { + cycleConsumer = consumer; + 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() + { + cycleConsumer = null; + iToV = null; + vToI = null; + blocked = null; + bSets = null; + stack = null; + } + + private void initMinSCGState() + { + index = 0; + foundSCCs = new ArrayList<>(); + vIndex = new HashMap<>(); + vLowlink = new HashMap<>(); + path = new ArrayDeque<>(); + pathSet = new HashSet<>(); + } + + private void clearMinSCCState() + { + index = 0; + foundSCCs = 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<>()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/PatonCycleBase.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/PatonCycleBase.java new file mode 100644 index 00000000000..d4e9e66b77a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/PatonCycleBase.java @@ -0,0 +1,161 @@ +/* + * (C) Copyright 2013-2023, 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 {@code 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasis.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasis.java new file mode 100644 index 00000000000..838da90d4c3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasis.java @@ -0,0 +1,104 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasis.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasis.java new file mode 100644 index 00000000000..f005ef4d0a4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasis.java @@ -0,0 +1,102 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/SzwarcfiterLauerSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/SzwarcfiterLauerSimpleCycles.java new file mode 100644 index 00000000000..5cc2bb99110 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/SzwarcfiterLauerSimpleCycles.java @@ -0,0 +1,275 @@ +/* + * (C) Copyright 2013-2023, 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.*; +import java.util.function.Consumer; + +/** + * 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 Consumer> cycleConsumer = 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 {@code 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 void findSimpleCycles(Consumer> consumer) + { + // Just a straightforward implementation of + // the algorithm. + if (graph == null) { + throw new IllegalArgumentException("Null graph."); + } + initState(consumer); + 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); + } + + clearState(); + } + + 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; + } + } + cycleConsumer.accept(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(Consumer> consumer) + { + cycleConsumer = consumer; + 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() + { + cycleConsumer = 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<>()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TarjanSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TarjanSimpleCycles.java new file mode 100644 index 00000000000..c3c67dabbfc --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TarjanSimpleCycles.java @@ -0,0 +1,192 @@ +/* + * (C) Copyright 2013-2023, 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.*; +import java.util.function.Consumer; + +/** + * 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 Consumer> cycleConsumer = null; + 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 {@code 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 void findSimpleCycles(Consumer> consumer) + { + if (graph == null) { + throw new IllegalArgumentException("Null graph."); + } + initState(consumer); + + for (V start : graph.vertexSet()) { + backtrack(start, start); + while (!markedStack.isEmpty()) { + marked.remove(markedStack.pop()); + } + } + + clearState(); + } + + 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()); + } + cycleConsumer.accept(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(Consumer> consumer) + { + cycleConsumer = consumer; + 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() + { + cycleConsumer = 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<>()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TiernanSimpleCycles.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TiernanSimpleCycles.java new file mode 100644 index 00000000000..2830a611fcf --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TiernanSimpleCycles.java @@ -0,0 +1,175 @@ +/* + * (C) Copyright 2013-2023, 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.*; +import java.util.function.Consumer; + +/** + * 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 {@code 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 void findSimpleCycles(Consumer> consumer) + { + if (graph == null) { + throw new IllegalArgumentException("Null graph."); + } + Map indices = new HashMap<>(); + List path = new ArrayList<>(); + Set pathSet = new HashSet<>(); + Map> blocked = new HashMap<>(); + + 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; + } + + 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); + consumer.accept(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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/WeakChordalityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/WeakChordalityInspector.java new file mode 100644 index 00000000000..a84c3bcaafa --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/WeakChordalityInspector.java @@ -0,0 +1,811 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/package-info.java new file mode 100644 index 00000000000..f412119a5b2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/package-info.java @@ -0,0 +1,93 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * 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; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecomposition.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecomposition.java new file mode 100644 index 00000000000..890b740dcda --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecomposition.java @@ -0,0 +1,321 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/HeavyPathDecomposition.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/HeavyPathDecomposition.java new file mode 100644 index 00000000000..2733afcb081 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/HeavyPathDecomposition.java @@ -0,0 +1,554 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java new file mode 100644 index 00000000000..4c949061b58 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for computing decompositions. + */ +package org.jgrapht.alg.decomposition; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithm.java new file mode 100644 index 00000000000..a632b5036ab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithm.java @@ -0,0 +1,123 @@ +/* + * (C) Copyright 2018-2023, 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(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmBase.java b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmBase.java new file mode 100644 index 00000000000..b5bd4544d91 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmBase.java @@ -0,0 +1,304 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight.java b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight.java new file mode 100644 index 00000000000..b92aee4caf1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight.java @@ -0,0 +1,116 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeights.java b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeights.java new file mode 100644 index 00000000000..e8d70cf1be4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeights.java @@ -0,0 +1,121 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/package-info.java new file mode 100644 index 00000000000..d625fa4d153 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for computing maximum density subgraphs. + */ +package org.jgrapht.alg.densesubgraph; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BarycenterGreedyTwoLayeredBipartiteLayout2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BarycenterGreedyTwoLayeredBipartiteLayout2D.java new file mode 100644 index 00000000000..20d48a87821 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BarycenterGreedyTwoLayeredBipartiteLayout2D.java @@ -0,0 +1,136 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BaseLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BaseLayoutAlgorithm2D.java new file mode 100644 index 00000000000..e9f12529b5b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BaseLayoutAlgorithm2D.java @@ -0,0 +1,98 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2D.java new file mode 100644 index 00000000000..9b54a51c7c4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2D.java @@ -0,0 +1,121 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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++; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2D.java new file mode 100644 index 00000000000..0882c1f8158 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2D.java @@ -0,0 +1,392 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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))); + + if (comparator.compare(vDisp.getX(), 0d) != 0 + || comparator.compare(vDisp.getY(), 0d) != 0) + { + 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.equals(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; + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRQuadTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRQuadTree.java new file mode 100644 index 00000000000..f5470d24b38 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRQuadTree.java @@ -0,0 +1,278 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2D.java new file mode 100644 index 00000000000..389ece1e29e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2D.java @@ -0,0 +1,202 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/LayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/LayoutAlgorithm2D.java new file mode 100644 index 00000000000..744db726093 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/LayoutAlgorithm2D.java @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/MedianGreedyTwoLayeredBipartiteLayout2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/MedianGreedyTwoLayeredBipartiteLayout2D.java new file mode 100644 index 00000000000..e39664f4bee --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/MedianGreedyTwoLayeredBipartiteLayout2D.java @@ -0,0 +1,135 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2D.java new file mode 100644 index 00000000000..1134d4518f8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2D.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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)); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2D.java new file mode 100644 index 00000000000..d45b4cebe77 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2D.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2D.java new file mode 100644 index 00000000000..9b92568a480 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2D.java @@ -0,0 +1,237 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Box2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Box2D.java new file mode 100644 index 00000000000..f87bd415e84 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Box2D.java @@ -0,0 +1,211 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 }); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Boxes.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Boxes.java new file mode 100644 index 00000000000..f05dc40f19f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Boxes.java @@ -0,0 +1,114 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/LayoutModel2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/LayoutModel2D.java new file mode 100644 index 00000000000..b31ce2f03ea --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/LayoutModel2D.java @@ -0,0 +1,120 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2D.java new file mode 100644 index 00000000000..e90ad74eb9e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2D.java @@ -0,0 +1,132 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/MapLayoutModel2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/MapLayoutModel2D.java new file mode 100644 index 00000000000..12467390269 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/MapLayoutModel2D.java @@ -0,0 +1,99 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Point2D.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Point2D.java new file mode 100644 index 00000000000..5c92e5b7137 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Point2D.java @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 + ")"; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Points.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Points.java new file mode 100644 index 00000000000..cad1ed05b87 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Points.java @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/package-info.java new file mode 100644 index 00000000000..5f1811ac868 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph Drawing Basic Types and Models. + */ +package org.jgrapht.alg.drawing.model; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/package-info.java new file mode 100644 index 00000000000..d6b4eb5cf71 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph Drawing. + */ +package org.jgrapht.alg.drawing; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImpl.java new file mode 100644 index 00000000000..4cc4018d516 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImpl.java @@ -0,0 +1,874 @@ +/* + * (C) Copyright 2020-2023, 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); + + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/DinicMFImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/DinicMFImpl.java new file mode 100644 index 00000000000..0399644e51e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/DinicMFImpl.java @@ -0,0 +1,263 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/EdmondsKarpMFImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/EdmondsKarpMFImpl.java new file mode 100644 index 00000000000..c797b3fb0bd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/EdmondsKarpMFImpl.java @@ -0,0 +1,294 @@ +/* + * (C) Copyright 2008-2023, 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 {@code MaximumFlow} instance to work with a copy of + * {@code network}. Current source and sink are set to {@code null}. If + * {@code network} is weighted, then capacities are weights, otherwise all capacities are + * equal to one. Doubles are compared using {@link #DEFAULT_EPSILON} tolerance. + * + * @param network network, where maximum flow will be calculated + */ + public EdmondsKarpMFImpl(Graph network) + { + this(network, DEFAULT_EPSILON); + } + + /** + * Constructs {@code MaximumFlow} instance to work with a copy of + * {@code network}. Current source and sink are set to {@code null}. If + * {@code 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 {@code source}, current sink to {@code sink}, then + * calculates maximum flow from {@code source} to {@code sink}. Note, that + * {@code source} and {@code sink} must be vertices of the {@code 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 {@code source}, current sink to {@code sink}, then + * calculates maximum flow from {@code source} to {@code sink}. Note, that + * {@code source} and {@code sink} must be vertices of the {@code 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 + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTree.java new file mode 100644 index 00000000000..45d4fb4f74f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTree.java @@ -0,0 +1,239 @@ +/* + * (C) Copyright 2016-2023, 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."); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTree.java new file mode 100644 index 00000000000..4287b325b73 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTree.java @@ -0,0 +1,390 @@ +/* + * (C) Copyright 2016-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmBase.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmBase.java new file mode 100644 index 00000000000..3df9c817ae9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmBase.java @@ -0,0 +1,479 @@ +/* + * (C) Copyright 2015-2023, 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 {@code null} if there was no + * {@code calculateMaximumFlow} calls. + * + * @return current source + */ + public V getCurrentSource() + { + return source; + } + + /** + * Returns current sink vertex, or {@code null} if there was no + * {@code calculateMaximumFlow} calls. + * + * @return current sink + */ + public V getCurrentSink() + { + return sink; + } + + /** + * Returns maximum flow value, that was calculated during last + * {@code calculateMaximumFlow} call. + * + * @return maximum flow value + */ + public double getMaximumFlowValue() + { + return maxFlowValue; + } + + /** + * Returns maximum flow, that was calculated during last + * {@code calculateMaximumFlow} call, or {@code null}, if there was no + * {@code 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()); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutset.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutset.java new file mode 100644 index 00000000000..4259f50c69b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutset.java @@ -0,0 +1,336 @@ +/* + * (C) Copyright 2016-2023, 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 {@code 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 {@code 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PushRelabelMFImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PushRelabelMFImpl.java new file mode 100644 index 00000000000..f9421052c56 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PushRelabelMFImpl.java @@ -0,0 +1,561 @@ +/* + * (C) Copyright 2015-2023, 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; + + /** + * @deprecated use {@link #setUseGlobalRelabelingHeuristic(boolean)} instead + */ + @Deprecated(since = "1.5.2", forRemoval = true) + public static boolean USE_GLOBAL_RELABELING_HEURISTIC = true; // @CS.suppress[StaticVariableName] + // TODO: make this static field private, rename it to "useGapRelabelingHeuristic" to comply + // with checkstyle naming rules and remove the // @CS.supress comment and in jgrapht_checks.xml + // the rule SuppressWithNearbyCommentFilter + /** + * @deprecated use {@link #setUseGapRelabelingHeuristic(boolean)} instead + */ + @Deprecated(since = "1.5.2", forRemoval = true) + public static boolean USE_GAP_RELABELING_HEURISTIC = true; // @CS.suppress[StaticVariableName] + // TODO: make this static field private, rename it to "useGapRelabelingHeuristic" to comply + // with checkstyle naming rules and remove the // @CS.supress comment and in jgrapht_checks.xml + // the rule SuppressWithNearbyCommentFilter + + public static void setUseGlobalRelabelingHeuristic(boolean useGlobalRelabelingHeuristic) + { + USE_GLOBAL_RELABELING_HEURISTIC = useGlobalRelabelingHeuristic; + } + + public static void setUseGapRelabelingHeuristic(boolean useGapRelabelingHeuristic) + { + USE_GAP_RELABELING_HEURISTIC = useGapRelabelingHeuristic; + } + + 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 {@code source}, current sink to {@code sink}, then + * calculates maximum flow from {@code source} to {@code sink}. Note, that + * {@code source} and {@code sink} must be vertices of the {@code 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlow.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlow.java new file mode 100644 index 00000000000..0d4ea9034f8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlow.java @@ -0,0 +1,934 @@ +/* + * (C) Copyright 2018-2023, 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.nextID = 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 temporarilyLabeledType = counter++; + int permanentlyLabeledType = 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 = permanentlyLabeledType; // 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 != permanentlyLabeledType) { + if (opposite.labelType == temporarilyLabeledType) { + // 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 = temporarilyLabeledType; + 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 nextID = 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 = nextID++; + + /** + * 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/MinimumCostFlowProblem.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/MinimumCostFlowProblem.java new file mode 100644 index 00000000000..3c9a600b24b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/MinimumCostFlowProblem.java @@ -0,0 +1,201 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/package-info.java new file mode 100644 index 00000000000..83b795c948d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for minimum cost flow + */ +package org.jgrapht.alg.flow.mincost; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/flow/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/package-info.java new file mode 100644 index 00000000000..f919c0e64d2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/flow/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Flow related algorithms. + */ +package org.jgrapht.alg.flow; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinder.java new file mode 100644 index 00000000000..6dcc4674792 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinder.java @@ -0,0 +1,133 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/package-info.java new file mode 100644 index 00000000000..e0ae01228c3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/package-info.java @@ -0,0 +1,23 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for Independent + * Set in a graph. + */ +package org.jgrapht.alg.independentset; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/AStarAdmissibleHeuristic.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/AStarAdmissibleHeuristic.java new file mode 100644 index 00000000000..d78f20bc138 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/AStarAdmissibleHeuristic.java @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2015-2023, 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) \le 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 {@code true} iff the heuristic is consistent wrt the {@code graph}, {@code 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 hX = getCostEstimate(edgeSource, targetVertex); + double hY = getCostEstimate(edgeTarget, targetVertex); + if (hX > weight + hY) + return false; + } + } + return true; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CapacitatedSpanningTreeAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CapacitatedSpanningTreeAlgorithm.java new file mode 100644 index 00000000000..940a67ffe6d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CapacitatedSpanningTreeAlgorithm.java @@ -0,0 +1,234 @@ +/* + * (C) Copyright 2018-2023, 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 {@code cmst} is a CMST on {@code graph} with root + * {@code root}, capacity {@code capacity} and demand function + * {@code 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 {@code 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 + "]"; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CliqueAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CliqueAlgorithm.java new file mode 100644 index 00000000000..4d36f9237f4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CliqueAlgorithm.java @@ -0,0 +1,83 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ClusteringAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ClusteringAlgorithm.java new file mode 100644 index 00000000000..8da8497fde3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ClusteringAlgorithm.java @@ -0,0 +1,108 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CycleBasisAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CycleBasisAlgorithm.java new file mode 100644 index 00000000000..eed6b73b4fd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CycleBasisAlgorithm.java @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EdgeScoringAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EdgeScoringAlgorithm.java new file mode 100644 index 00000000000..35d1936d39c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EdgeScoringAlgorithm.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EulerianCycleAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EulerianCycleAlgorithm.java new file mode 100644 index 00000000000..fc32d664497 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/EulerianCycleAlgorithm.java @@ -0,0 +1,45 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/FlowAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/FlowAlgorithm.java new file mode 100644 index 00000000000..2de3869cab3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/FlowAlgorithm.java @@ -0,0 +1,122 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleAlgorithm.java new file mode 100644 index 00000000000..522108e7ae4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleAlgorithm.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2018-2023, 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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleImprovementAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleImprovementAlgorithm.java new file mode 100644 index 00000000000..b7de988ea5e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/HamiltonianCycleImprovementAlgorithm.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2018-2023, 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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/IndependentSetAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/IndependentSetAlgorithm.java new file mode 100644 index 00000000000..64f588ee299 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/IndependentSetAlgorithm.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/KShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/KShortestPathAlgorithm.java new file mode 100644 index 00000000000..866a758f0ad --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/KShortestPathAlgorithm.java @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LinkPredictionAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LinkPredictionAlgorithm.java new file mode 100644 index 00000000000..12b2be6bfe8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LinkPredictionAlgorithm.java @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LowestCommonAncestorAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LowestCommonAncestorAlgorithm.java new file mode 100644 index 00000000000..07c3abe3309 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/LowestCommonAncestorAlgorithm.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2018-2023, 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()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ManyToManyShortestPathsAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ManyToManyShortestPathsAlgorithm.java new file mode 100644 index 00000000000..e8c9f08e500 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ManyToManyShortestPathsAlgorithm.java @@ -0,0 +1,153 @@ +/* + * (C) Copyright 2019-2023, 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"); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MatchingAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MatchingAlgorithm.java new file mode 100644 index 00000000000..b5e290981fb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MatchingAlgorithm.java @@ -0,0 +1,194 @@ +/* + * (C) Copyright 2013-2023, 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 + "]"; + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximalCliqueEnumerationAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximalCliqueEnumerationAlgorithm.java new file mode 100644 index 00000000000..337619c9622 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximalCliqueEnumerationAlgorithm.java @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumDensitySubgraphAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumDensitySubgraphAlgorithm.java new file mode 100644 index 00000000000..081da1bf3ab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumDensitySubgraphAlgorithm.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2018-2023, 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(); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumFlowAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumFlowAlgorithm.java new file mode 100644 index 00000000000..8ba4555835b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MaximumFlowAlgorithm.java @@ -0,0 +1,115 @@ +/* + * (C) Copyright 2015-2023, 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 {@code source}, current sink to {@code sink}, then + * calculates maximum flow from {@code source} to {@code 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 {@code source}, current sink to {@code sink}, then + * calculates maximum flow from {@code source} to {@code sink}. Note, that + * {@code source} and {@code sink} must be vertices of the {@code 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCostFlowAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCostFlowAlgorithm.java new file mode 100644 index 00000000000..133972c7d6a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCostFlowAlgorithm.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCycleMeanAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCycleMeanAlgorithm.java new file mode 100644 index 00000000000..590a9172f1b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumCycleMeanAlgorithm.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2020-2023, 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(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumSTCutAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumSTCutAlgorithm.java new file mode 100644 index 00000000000..aae68942b4d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MinimumSTCutAlgorithm.java @@ -0,0 +1,87 @@ +/* + * (C) Copyright 2016-2023, 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(); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MultiObjectiveShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MultiObjectiveShortestPathAlgorithm.java new file mode 100644 index 00000000000..a0e9ceb854a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MultiObjectiveShortestPathAlgorithm.java @@ -0,0 +1,83 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PartitioningAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PartitioningAlgorithm.java new file mode 100644 index 00000000000..ed88b220908 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PartitioningAlgorithm.java @@ -0,0 +1,185 @@ +/* + * (C) Copyright 2018-2023, 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 + * ({@code 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PlanarityTestingAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PlanarityTestingAlgorithm.java new file mode 100644 index 00000000000..4cc48f2c13a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/PlanarityTestingAlgorithm.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2018-2023, 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ShortestPathAlgorithm.java new file mode 100644 index 00000000000..0f6cacdc501 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ShortestPathAlgorithm.java @@ -0,0 +1,103 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpannerAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpannerAlgorithm.java new file mode 100644 index 00000000000..61ae20c2bf3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpannerAlgorithm.java @@ -0,0 +1,100 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 + "]"; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpanningTreeAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpanningTreeAlgorithm.java new file mode 100644 index 00000000000..ef8fe2b0619 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpanningTreeAlgorithm.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2013-2023, 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 + "]"; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SteinerTreeAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SteinerTreeAlgorithm.java new file mode 100644 index 00000000000..e11ef47eff4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SteinerTreeAlgorithm.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2025, by Lena Büttel and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.Serializable; +import java.util.Iterator; +import java.util.Set; + +/** + * An algorithm which computes a + * Steiner tree of a given graph. A + * Steiner tree is a tree that connects a given set of vertices (called Steiner points or terminals) + * with minimum total weight, possibly using additional vertices not in the original set. + * + * @param the graph vertices type + * @param the graph edge type + */ +public interface SteinerTreeAlgorithm +{ + /** + * Computes a Steiner tree. + * + * @param steinerPoints the set of vertices (terminals) that must be connected by the Steiner + * tree + * @return a Steiner tree connecting all the specified vertices + */ + SteinerTree getSteinerTree(Set steinerPoints); + + /** + * A Steiner tree. + * + * @param the graph edge type + */ + interface SteinerTree + extends + Iterable + { + /** + * Returns the weight of the Steiner tree. + * + * @return weight of the Steiner tree + */ + double getWeight(); + + /** + * Set of edges of the Steiner tree. + * + * @return edge set of the Steiner tree + */ + Set getEdges(); + + /** + * Returns an iterator over the edges in the Steiner tree. + * + * @return iterator over the edges in the Steiner tree. + */ + @Override + default Iterator iterator() + { + return getEdges().iterator(); + } + } + + /** + * Default implementation of the Steiner tree interface. + * + * @param the graph edge type + */ + class SteinerTreeImpl + implements + SteinerTree, + Serializable + { + private static final long serialVersionUID = 402707108331703333L; + + private final double weight; + private final Set edges; + + /** + * Construct a new Steiner tree. + * + * @param edges the edges + * @param weight the weight + */ + public SteinerTreeImpl(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 "Steiner-Tree [weight=" + weight + ", edges=" + edges + "]"; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/StrongConnectivityAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/StrongConnectivityAlgorithm.java new file mode 100644 index 00000000000..7f784464de0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/StrongConnectivityAlgorithm.java @@ -0,0 +1,74 @@ +/* + * (C) Copyright 2013-2023, 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 {@code List} of {@code 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(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/TreeToPathDecompositionAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/TreeToPathDecompositionAlgorithm.java new file mode 100644 index 00000000000..9fab341e0a7 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/TreeToPathDecompositionAlgorithm.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2018-2023, 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() + "]"; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexColoringAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexColoringAlgorithm.java new file mode 100644 index 00000000000..596534cbd19 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexColoringAlgorithm.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 + "]"; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexCoverAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexCoverAlgorithm.java new file mode 100644 index 00000000000..5eb8c661131 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexCoverAlgorithm.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexScoringAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexScoringAlgorithm.java new file mode 100644 index 00000000000..f1030042750 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/VertexScoringAlgorithm.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/package-info.java new file mode 100644 index 00000000000..4aa4b88f2ab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Algorithm related interfaces. + */ +package org.jgrapht.alg.interfaces; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspector.java new file mode 100644 index 00000000000..0994c1e0635 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspector.java @@ -0,0 +1,207 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspector.java new file mode 100644 index 00000000000..ec1135fbbb8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspector.java @@ -0,0 +1,304 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspector.java new file mode 100644 index 00000000000..7a362f84b60 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspector.java @@ -0,0 +1,166 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspector.java new file mode 100644 index 00000000000..ca97291c742 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspector.java @@ -0,0 +1,477 @@ +/* + * (C) Copyright 2018-2023, 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 + * {@code graph1} and {@code 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 + * {@code 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 + * {@code 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 {@code graph1} and {@code 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/GraphOrdering.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/GraphOrdering.java new file mode 100644 index 00000000000..e4a4b9f70ee --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/GraphOrdering.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2015-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMapping.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMapping.java new file mode 100644 index 00000000000..f2c1cbf7cf2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMapping.java @@ -0,0 +1,356 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismInspector.java new file mode 100644 index 00000000000..51f57746aae --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismInspector.java @@ -0,0 +1,49 @@ +/* + * (C) Copyright 2015-2023, 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(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismUndecidableException.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismUndecidableException.java new file mode 100644 index 00000000000..a872749bdae --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/IsomorphismUndecidableException.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2AbstractIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2AbstractIsomorphismInspector.java new file mode 100644 index 00000000000..8337c33045c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2AbstractIsomorphismInspector.java @@ -0,0 +1,135 @@ +/* + * (C) Copyright 2015-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspector.java new file mode 100644 index 00000000000..c3e955defa7 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspector.java @@ -0,0 +1,101 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismState.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismState.java new file mode 100644 index 00000000000..d95148260f0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismState.java @@ -0,0 +1,230 @@ +/* + * (C) Copyright 2015-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphMappingIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphMappingIterator.java new file mode 100644 index 00000000000..20aa793e924 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2GraphMappingIterator.java @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2015-2023, 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2MappingIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2MappingIterator.java new file mode 100644 index 00000000000..0efc6d90186 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2MappingIterator.java @@ -0,0 +1,93 @@ +/* + * (C) Copyright 2015-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2State.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2State.java new file mode 100644 index 00000000000..12548cb06b0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2State.java @@ -0,0 +1,431 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspector.java new file mode 100644 index 00000000000..ad209052124 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspector.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismState.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismState.java new file mode 100644 index 00000000000..2226cc3cfd2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismState.java @@ -0,0 +1,227 @@ +/* + * (C) Copyright 2015-2023, 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); + final String fp = "isFeasiblePair"; + 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( + fp, 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( + fp, 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(fp, 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( + fp, + 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( + fp, + 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(fp, 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(fp, abortmsg + ": " + cause); + } + + return false; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphMappingIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphMappingIterator.java new file mode 100644 index 00000000000..efe2ad0ebb6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2SubgraphMappingIterator.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2015-2023, 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/package-info.java new file mode 100644 index 00000000000..ff4c2aaf40d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Algorithms for (sub)graph isomorphism. + */ +package org.jgrapht.alg.isomorphism; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinder.java new file mode 100644 index 00000000000..69178bde420 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinder.java @@ -0,0 +1,253 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinder.java new file mode 100644 index 00000000000..6e0d1b842b1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinder.java @@ -0,0 +1,284 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/HeavyPathLCAFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/HeavyPathLCAFinder.java new file mode 100644 index 00000000000..9dca9f6cc01 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/HeavyPathLCAFinder.java @@ -0,0 +1,199 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/NaiveLCAFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/NaiveLCAFinder.java new file mode 100644 index 00000000000..34c1c3d7374 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/NaiveLCAFinder.java @@ -0,0 +1,174 @@ +/* + * (C) Copyright 2013-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/TarjanLCAFinder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/TarjanLCAFinder.java new file mode 100644 index 00000000000..9f077e4f180 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/TarjanLCAFinder.java @@ -0,0 +1,245 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/lca/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/package-info.java new file mode 100644 index 00000000000..bc36bd30e6e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/lca/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for computing lowest common ancestors in graphs. + */ +package org.jgrapht.alg.lca; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPrediction.java new file mode 100644 index 00000000000..49e397b95ba --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPrediction.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPrediction.java new file mode 100644 index 00000000000..a82b895a61d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPrediction.java @@ -0,0 +1,78 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPrediction.java new file mode 100644 index 00000000000..99098e62d4a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPrediction.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPrediction.java new file mode 100644 index 00000000000..9b369764a43 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPrediction.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPrediction.java new file mode 100644 index 00000000000..848d8dd102c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPrediction.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPrediction.java new file mode 100644 index 00000000000..faa5ec3c9d5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPrediction.java @@ -0,0 +1,84 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LinkPredictionIndexNotWellDefinedException.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LinkPredictionIndexNotWellDefinedException.java new file mode 100644 index 00000000000..f83bdc6b971 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/LinkPredictionIndexNotWellDefinedException.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentLinkPrediction.java new file mode 100644 index 00000000000..913ea36ab06 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentLinkPrediction.java @@ -0,0 +1,72 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPrediction.java new file mode 100644 index 00000000000..3d921cd055c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPrediction.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPrediction.java new file mode 100644 index 00000000000..72fe39769ff --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPrediction.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPrediction.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPrediction.java new file mode 100644 index 00000000000..b774daa284b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPrediction.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 SorensenIndexLinkPrediction + implements LinkPredictionAlgorithm +{ + private Graph graph; + + /** + * Create a new prediction + * + * @param graph the input graph + */ + public SorensenIndexLinkPrediction(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); + } + +} diff --git "a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPrediction.java" "b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPrediction.java" new file mode 100644 index 00000000000..8ccaca7a46f --- /dev/null +++ "b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPrediction.java" @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 + * + * @deprecated Class will be replaced by SorensenIndexLinkPrediction + */ +@Deprecated +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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/package-info.java new file mode 100644 index 00000000000..50b89dce827 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Algorithms for link prediction + */ +package org.jgrapht.alg.linkprediction; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatching.java new file mode 100644 index 00000000000..790ece78024 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatching.java @@ -0,0 +1,693 @@ +/* + * (C) Copyright 2017-2023, 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--; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatching.java new file mode 100644 index 00000000000..acbbfbe0fa6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatching.java @@ -0,0 +1,121 @@ +/* + * (C) Copyright 2017-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyWeightedMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyWeightedMatching.java new file mode 100644 index 00000000000..9178e0d363d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/GreedyWeightedMatching.java @@ -0,0 +1,151 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatching.java new file mode 100644 index 00000000000..c814430d645 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatching.java @@ -0,0 +1,227 @@ +/* + * (C) Copyright 2017-2023, 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 static final int DUMMY = 0; + /* Infinity */ + private static 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()); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatching.java new file mode 100644 index 00000000000..30fae1c2f2f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatching.java @@ -0,0 +1,625 @@ +/* + * (C) Copyright 2013-2023, 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; + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatching.java new file mode 100644 index 00000000000..5234618b647 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatching.java @@ -0,0 +1,377 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/PathGrowingWeightedMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/PathGrowingWeightedMatching.java new file mode 100644 index 00000000000..44c5657e8fc --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/PathGrowingWeightedMatching.java @@ -0,0 +1,408 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + matchedVertices.add(s); + matchedVertices.add(t); + 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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatching.java new file mode 100644 index 00000000000..8fc606e2d39 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatching.java @@ -0,0 +1,656 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdater.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdater.java new file mode 100644 index 00000000000..12186a95f2d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdater.java @@ -0,0 +1,436 @@ +/* + * (C) Copyright 2018-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVEdge.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVEdge.java new file mode 100644 index 00000000000..5079000034b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVEdge.java @@ -0,0 +1,333 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializer.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializer.java new file mode 100644 index 00000000000..e1d038a8f4d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializer.java @@ -0,0 +1,964 @@ +/* + * (C) Copyright 2018-2023, 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, + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNode.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNode.java new file mode 100644 index 00000000000..666db8e7111 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNode.java @@ -0,0 +1,606 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVOptions.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVOptions.java new file mode 100644 index 00000000000..6e6ff9deab4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVOptions.java @@ -0,0 +1,252 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdater.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdater.java new file mode 100644 index 00000000000..db2c64546ef --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdater.java @@ -0,0 +1,1162 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVState.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVState.java new file mode 100644 index 00000000000..a8e32c4d68d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVState.java @@ -0,0 +1,130 @@ +/* + * (C) Copyright 2018-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTree.java new file mode 100644 index 00000000000..f5115a85b1a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTree.java @@ -0,0 +1,481 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdge.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdge.java new file mode 100644 index 00000000000..c7445cdc4fe --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdge.java @@ -0,0 +1,226 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatching.java new file mode 100644 index 00000000000..6b749efb227 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatching.java @@ -0,0 +1,218 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatching.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatching.java new file mode 100644 index 00000000000..2ddcf99a4b8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatching.java @@ -0,0 +1,1053 @@ +/* + * (C) Copyright 2018-2023, 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/ObjectiveSense.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/ObjectiveSense.java new file mode 100644 index 00000000000..2609ab43806 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/ObjectiveSense.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2018-2023, 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 +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/package-info.java new file mode 100644 index 00000000000..733e2e9af12 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 for Kolmogorov's Blossom V algorithm + */ +package org.jgrapht.alg.matching.blossom.v5; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/matching/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/package-info.java new file mode 100644 index 00000000000..9cc7a54d18d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/matching/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Algorithms for the computation of matchings. + */ +package org.jgrapht.alg.matching; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/package-info.java new file mode 100644 index 00000000000..78a4a7bb074 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Algorithms provided with JGraphT. + */ +package org.jgrapht.alg; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/package.html b/jgrapht-core/src/main/java/org/jgrapht/alg/package.html deleted file mode 100644 index 041cd4ee292..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Algorithms provided with JGraphT. - - \ No newline at end of file diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/partition/BipartitePartitioning.java b/jgrapht-core/src/main/java/org/jgrapht/alg/partition/BipartitePartitioning.java new file mode 100644 index 00000000000..bd07601e7a8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/partition/BipartitePartitioning.java @@ -0,0 +1,171 @@ +/* + * (C) Copyright 2016-2023, 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 LinkedHashSet<>(graph.vertexSet()); + Set odd = new LinkedHashSet<>(); + 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 LinkedHashSet<>(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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/partition/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/partition/package-info.java new file mode 100644 index 00000000000..46fa9fe5382 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/partition/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithm for computing partitions. + */ +package org.jgrapht.alg.partition; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspector.java b/jgrapht-core/src/main/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspector.java new file mode 100644 index 00000000000..622a80cec48 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspector.java @@ -0,0 +1,2411 @@ +/* + * (C) Copyright 2018-2023, 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(); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/planar/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/planar/package-info.java new file mode 100644 index 00000000000..47f839c016e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/planar/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Algorithms for testing planarity of the graphs + */ +package org.jgrapht.alg.planar; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ApBetweennessCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ApBetweennessCentrality.java new file mode 100644 index 00000000000..ab52a9abf52 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ApBetweennessCentrality.java @@ -0,0 +1,340 @@ +/* + * (C) Copyright 2017-2023, 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.apfloat.Apfloat; +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 with arbitrary precision arithmetic. + * + *

    + * 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 thus it is not safe to assume that arithmetic takes + * constant time. + * + * This class uses arbitrary precision arithmetic (except for the execution of Dijkstra's + * algorithm). The precision can be adjusted by the constructor parameters. + * + * @param the graph vertex type + * @param the graph edge type + * + * @author Assaf Mizrachi + */ +public class ApBetweennessCentrality + implements VertexScoringAlgorithm +{ + /** + * Underlying graph + */ + private final Graph graph; + /** + * Whether to normalize scores + */ + private final boolean normalize; + /** + * The actual scores + */ + private Map scores; + + /** + * Precision for arbitrary precision arithmetic. + */ + private final long precision; + + /** + * Construct a new instance. + * + * @param graph the input graph + */ + public ApBetweennessCentrality(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 ApBetweennessCentrality(Graph graph, boolean normalize) + { + this(graph, normalize, Apfloat.INFINITE); + } + + /** + * 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 precision precision for arbitrary precision arithmetic + */ + public ApBetweennessCentrality(Graph graph, boolean normalize, long precision) + { + this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); + this.scores = null; + this.normalize = normalize; + this.precision = precision; + } + + /** + * {@inheritDoc} + */ + @Override + public Map getScores() + { + if (scores == null) { + compute(); + } + return Collections.unmodifiableMap(scores); + } + + /** + * {@inheritDoc} + */ + @Override + public Apfloat 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.iterables().vertices().forEach(v -> scores.put(v, new Apfloat(0l, precision))); + + // compute for each source + graph.iterables().vertices().forEach(this::compute); + + // For undirected graph, divide scores by two as each shortest path + // considered twice. + final Apfloat two = new Apfloat(2l, precision); + if (!graph.getType().isDirected()) { + scores.forEach((v, score) -> scores.put(v, score.divide(two))); + } + + final Apfloat one = new Apfloat(1l, precision); + if (normalize) { + long n = graph.iterables().vertexCount(); + if (n > 2) { + Apfloat apn = new Apfloat(n, precision); + Apfloat nf = apn.subtract(one).multiply(apn.subtract(two)); + scores.forEach((v, score) -> scores.put(v, score.divide(nf))); + } + } + } + + private void compute(V s) + { + // initialize + ArrayDeque stack = new ArrayDeque<>(); + Map> predecessors = new HashMap<>(); + graph.iterables().vertices().forEach(w -> predecessors.put(w, new ArrayList<>())); + + // Number of shortest paths from s to v + Map sigma = new HashMap<>(); + final Apfloat zero = new Apfloat(0l, precision); + final Apfloat one = new Apfloat(1l, precision); + graph.iterables().vertices().forEach(t -> sigma.put(t, zero)); + sigma.put(s, one); + + // Distance (weight) of the shortest path from s to v + Map distance = new HashMap<>(); + graph.iterables().vertices().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 : this.graph.outgoingEdgesOf(v)) { + V w = Graphs.getOppositeVertex(this.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); + sigma.put(w, sigma.get(w).add(sigma.get(v))); + 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.iterables().vertices().forEach(v -> dependency.put(v, zero)); + // 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).add( + sigma.get(v).divide(sigma.get(w)).multiply(dependency.get(w).add(one)))); + } + if (!w.equals(s)) { + scores.put(w, scores.get(w).add(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(); + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/BetweennessCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/BetweennessCentrality.java new file mode 100644 index 00000000000..99d258e8e8e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/BetweennessCentrality.java @@ -0,0 +1,359 @@ +/* + * (C) Copyright 2017-2023, 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(); + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClosenessCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClosenessCentrality.java new file mode 100644 index 00000000000..617a4446892 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClosenessCentrality.java @@ -0,0 +1,202 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClusteringCoefficient.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClusteringCoefficient.java new file mode 100644 index 00000000000..ded163ae9ff --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClusteringCoefficient.java @@ -0,0 +1,234 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/Coreness.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/Coreness.java new file mode 100644 index 00000000000..dd09da4c8b1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/Coreness.java @@ -0,0 +1,175 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 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); + } + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EdgeBetweennessCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EdgeBetweennessCentrality.java new file mode 100644 index 00000000000..6e192e9e19a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EdgeBetweennessCentrality.java @@ -0,0 +1,337 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EigenvectorCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EigenvectorCentrality.java new file mode 100644 index 00000000000..b1843a74539 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EigenvectorCentrality.java @@ -0,0 +1,202 @@ +/* + * (C) Copyright 2020-2023, 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 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--; + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/HarmonicCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/HarmonicCentrality.java new file mode 100644 index 00000000000..90aaa6586b0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/HarmonicCentrality.java @@ -0,0 +1,115 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 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); + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/KatzCentrality.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/KatzCentrality.java new file mode 100644 index 00000000000..e52c157788f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/KatzCentrality.java @@ -0,0 +1,284 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 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--; + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/PageRank.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/PageRank.java new file mode 100644 index 00000000000..0d6bd4d8756 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/PageRank.java @@ -0,0 +1,380 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 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; + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/package-info.java new file mode 100644 index 00000000000..1cc81505e42 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2017-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Vertex and/or edge scoring algorithms. + */ +package org.jgrapht.alg.scoring; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristic.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristic.java new file mode 100644 index 00000000000..4eb392fa190 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristic.java @@ -0,0 +1,211 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AStarShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AStarShortestPath.java new file mode 100644 index 00000000000..9c5360e1059 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AStarShortestPath.java @@ -0,0 +1,259 @@ +/* + * (C) Copyright 2015-2023, 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 = gScoreMap.get(currentNode.getValue()); + double tentativeGScore = gScore + 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AllDirectedPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AllDirectedPaths.java new file mode 100644 index 00000000000..f3d2a60d9d0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AllDirectedPaths.java @@ -0,0 +1,344 @@ +/* + * (C) Copyright 2015-2023, 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; + + /** + * Provides validation for the paths which will be computed. If the validator is {@code null}, + * this means that all paths are valid. + */ + private final PathValidator pathValidator; + + /** + * Create a new instance. + * + * @param graph the input graph + * @throws IllegalArgumentException if the graph is not directed + */ + public AllDirectedPaths(Graph graph) + { + this(graph, null); + } + + /** + * Create a new instance with given {@code pathValidator}. + * + * If non-{@code null}, the {@code pathValidator} will be used while searching for paths, + * validating the addition of any edge to a partial path. Zero-length paths will therefore not + * be subject to {@code pathValidator}; length-1 paths will. + * + * @param graph the input graph + * @param pathValidator validator for computed paths; may be null + * @throws IllegalArgumentException if the graph is not directed + */ + public AllDirectedPaths(Graph graph, PathValidator pathValidator) + { + this.graph = GraphTests.requireDirected(graph); + this.pathValidator = pathValidator; + } + + /** + * 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)) { + // pathValidator intentionally not invoked here + 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 (pathValidator == null + || pathValidator.isValidPath(GraphWalk.emptyWalk(graph), edge)) + { + 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 requested, validate the path + if (pathValidator != null + && !pathValidator.isValidPath(makePath(incompletePath), 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BFSShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BFSShortestPath.java new file mode 100644 index 00000000000..a7e8e90d206 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BFSShortestPath.java @@ -0,0 +1,127 @@ +/* + * (C) Copyright 2018-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseBidirectionalShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseBidirectionalShortestPathAlgorithm.java new file mode 100644 index 00000000000..d9c164dba17 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseBidirectionalShortestPathAlgorithm.java @@ -0,0 +1,140 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseKDisjointShortestPathsAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseKDisjointShortestPathsAlgorithm.java new file mode 100755 index 00000000000..08cf9cc0d1e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseKDisjointShortestPathsAlgorithm.java @@ -0,0 +1,267 @@ +/* + * (C) Copyright 2018-2023, 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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPaths.java new file mode 100644 index 00000000000..c87d17ed7f1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPaths.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2019-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseMultiObjectiveShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseMultiObjectiveShortestPathAlgorithm.java new file mode 100644 index 00000000000..3036cf7a718 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseMultiObjectiveShortestPathAlgorithm.java @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseShortestPathAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseShortestPathAlgorithm.java new file mode 100644 index 00000000000..d38d5a12c53 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BaseShortestPathAlgorithm.java @@ -0,0 +1,115 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPath.java new file mode 100644 index 00000000000..f9afa8c6300 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPath.java @@ -0,0 +1,255 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPaths.java new file mode 100644 index 00000000000..c358c4a7175 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPaths.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2018-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPath.java new file mode 100644 index 00000000000..5fe11afee85 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPath.java @@ -0,0 +1,384 @@ +/* + * (C) Copyright 2019-2023, 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 = frontier.getDistance(v); + double tentativeGScore = gScore + 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 + 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPath.java new file mode 100644 index 00000000000..451cce3e918 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPath.java @@ -0,0 +1,266 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPaths.java new file mode 100644 index 00000000000..e46a74185dd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPaths.java @@ -0,0 +1,485 @@ +/* + * (C) Copyright 2019-2023, 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} 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; + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstra.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstra.java new file mode 100644 index 00000000000..5af18b04a77 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstra.java @@ -0,0 +1,346 @@ +/* + * (C) Copyright 2019-2023, 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} 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputation.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputation.java new file mode 100644 index 00000000000..63d273729cb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputation.java @@ -0,0 +1,1295 @@ +/* + * (C) Copyright 2019-2023, 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} 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}, {@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}, + * {@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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPaths.java new file mode 100644 index 00000000000..95701cd1fee --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPaths.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2019-2023, 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPath.java new file mode 100644 index 00000000000..6e394d2d7ac --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPath.java @@ -0,0 +1,803 @@ +/* + * (C) Copyright 2018-2023, 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.*; +import java.util.function.Supplier; + +/** + * 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; + /** + * Comparator for vertices in the graph which is used to create {@code ConcurrentSkipListSet} + * instances for the {@code bucketStructure}. + */ + private Comparator vertexComparator; + /** + * Supplier of the buckets for the {@code bucketStructure}. + */ + private Supplier> bucketsSupplier; + + /** + * 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 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 {@code graph}, {@code executor} and + * {@code vertexComparator}. 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}. {@code vertexComparator} provided via + * this constructor is used to create instances of {@code ConcurrentSkipListSet} for the + * individual buckets. This gives a gives a small performance benefit for shortest paths + * computation. + * + * @param graph graph + * @param executor executor which will be used for parallelization + * @param vertexComparator comparator for vertices of the {@code graph} + */ + public DeltaSteppingShortestPath( + Graph graph, ThreadPoolExecutor executor, Comparator vertexComparator) + { + this(graph, 0.0, executor, vertexComparator); + } + + /** + * 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); + Objects.requireNonNull(executor, "executor must not be null!"); + init(graph, delta, executor, null); + } + + /** + * Constructs a new instance of the algorithm for a given graph, delta, {@code executor} and + * {@code vertexComparator}. 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}. {@code vertexComparator} provided via + * this constructor is used to create instances of {@code ConcurrentSkipListSet} for the + * individual buckets. This gives a gives a small performance benefit for shortest paths + * computation. + * + * @param graph the graph + * @param delta bucket width + * @param executor executor which will be used for parallelization + * @param vertexComparator comparator for vertices of the {@code graph} + */ + public DeltaSteppingShortestPath( + Graph graph, double delta, ThreadPoolExecutor executor, + Comparator vertexComparator) + { + super(graph); + Objects.requireNonNull(executor, "executor must not be null!"); + Objects.requireNonNull(executor, "vertexComparator must not be null!"); + init(graph, delta, executor, vertexComparator); + } + + /** + * 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), null); + } + + /** + * 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, + Comparator vertexComparator) + { + if (delta < 0) { + throw new IllegalArgumentException(DELTA_MUST_BE_NON_NEGATIVE); + } + this.delta = delta; + this.parallelism = executor.getMaximumPoolSize(); + this.vertexComparator = vertexComparator; + 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); + bucketsSupplier = getBucketsSupplier(source); + for (int i = 0; i < numOfBuckets; i++) { + bucketStructure.add(bucketsSupplier.get()); + } + fillDistanceAndPredecessorMap(); + + computeShortestPaths(source); + + return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap); + } + + /** + * Creates a supplier of sets for the {@code bucketStructure}. + * + * @param vertex a vertex in the graph + * @return supplier of buckets + */ + private Supplier> getBucketsSupplier(V vertex) + { + if (vertexComparator != null) { + return () -> new ConcurrentSkipListSet<>(vertexComparator); + } else if (vertex instanceof Comparable) { + return () -> new ConcurrentSkipListSet<>(); + } else { + return () -> Collections.newSetFromMap(new ConcurrentHashMap<>()); + } + } + + /** + * 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} with a new instance of the concurrent set. + * 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, bucketsSupplier.get()); + 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)); + } + } + } + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIterator.java new file mode 100644 index 00000000000..5528b5e1b7d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIterator.java @@ -0,0 +1,230 @@ +/* + * (C) Copyright 2003-2023, 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)); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPaths.java new file mode 100644 index 00000000000..41ea75695c2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPaths.java @@ -0,0 +1,167 @@ +/* + * (C) Copyright 2019-2023, 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); + if (reversedPath == null) { + return null; + } + 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraShortestPath.java new file mode 100644 index 00000000000..f57a9754aa4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/DijkstraShortestPath.java @@ -0,0 +1,166 @@ +/* + * (C) Copyright 2003-2023, 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(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPath.java new file mode 100644 index 00000000000..31c5c751f5f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPath.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2019-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIterator.java new file mode 100644 index 00000000000..9ec03646575 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIterator.java @@ -0,0 +1,752 @@ +/* + * (C) Copyright 2019-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java new file mode 100644 index 00000000000..251a511fe22 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPaths.java @@ -0,0 +1,396 @@ +/* + * (C) Copyright 2009-2023, 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 vA = vertexIndices.get(a); + int vB = vertexIndices.get(b); + + if (backtrace[vA][vB] == null) { // No path exists + return createEmptyPath(a, b); + } + + // Reconstruct the path + List edges = new ArrayList<>(); + V u = a; + while (!u.equals(b)) { + int vU = vertexIndices.get(u); + E e = TypeUtil.uncheckedCast(backtrace[vU][vB]); + edges.add(e); + u = Graphs.getOppositeVertex(graph, e, u); + } + return new GraphWalk<>(graph, a, b, null, edges, d[vA][vB]); + } + + /** + * {@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 vA = vertexIndices.get(a); + int vB = vertexIndices.get(b); + + if (backtrace[vA][vB] == null) { // No path exists + return null; + } else { + E e = TypeUtil.uncheckedCast(backtrace[vA][vB]); + 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 vA = vertexIndices.get(a); + int vB = vertexIndices.get(b); + + if (backtrace[vA][vB] == null) { // No path exists + return null; + } else { + populateLastHopMatrix(); + E e = TypeUtil.uncheckedCast(lastHopMatrix[vA][vB]); + 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 v1 = vertexIndices.get(source); + int v2 = vertexIndices.get(target); + double edgeWeight = graph.getEdgeWeight(edge); + if (Double.compare(edgeWeight, d[v1][v2]) < 0) { + d[v1][v2] = d[v2][v1] = edgeWeight; + backtrace[v1][v2] = edge; + backtrace[v2][v1] = 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 i1 = vertexIndices.get(v1); + for (E e : graph.outgoingEdgesOf(v1)) { + V v2 = Graphs.getOppositeVertex(graph, e, v1); + if (!v1.equals(v2)) { + int i2 = vertexIndices.get(v2); + double edgeWeight = graph.getEdgeWeight(e); + if (Double.compare(edgeWeight, d[i1][i2]) < 0) { + d[i1][i2] = edgeWeight; + backtrace[i1][i2] = 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 sumIKKJ = d[i][k] + d[k][j]; + if (Double.compare(sumIKKJ, d[i][j]) < 0) { + d[i][j] = sumIKKJ; + 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 vU = vertexIndices.get(u); + E e = TypeUtil.uncheckedCast(backtrace[vU][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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/GraphMeasurer.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/GraphMeasurer.java new file mode 100644 index 00000000000..8fc6b7b0447 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/GraphMeasurer.java @@ -0,0 +1,233 @@ +/* + * (C) Copyright 2017-2023, 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)); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPath.java new file mode 100644 index 00000000000..4bf244ef96c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPath.java @@ -0,0 +1,398 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/JohnsonShortestPaths.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/JohnsonShortestPaths.java new file mode 100644 index 00000000000..d6292cd8872 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/JohnsonShortestPaths.java @@ -0,0 +1,399 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListMultiObjectiveSingleSourcePathsImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListMultiObjectiveSingleSourcePathsImpl.java new file mode 100644 index 00000000000..d4b5863abb9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListMultiObjectiveSingleSourcePathsImpl.java @@ -0,0 +1,98 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsImpl.java new file mode 100644 index 00000000000..a16ec1ed5e7 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsImpl.java @@ -0,0 +1,127 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/MartinShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/MartinShortestPath.java new file mode 100644 index 00000000000..9ea6a8fc6c8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/MartinShortestPath.java @@ -0,0 +1,288 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 = calculateModifiedWeight( + this.workingGraph.getEdgeWeight(edge), singleSourcePaths.getWeight(source), + singleSourcePaths.getWeight(target)); + + 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); + } + + private double calculateModifiedWeight( + double edgeWeight, double sourcePathWeight, double targetPathWeight) + { + if (sourcePathWeight == Double.POSITIVE_INFINITY + && targetPathWeight == Double.POSITIVE_INFINITY) + { + return Double.NaN; + } else if (sourcePathWeight == Double.POSITIVE_INFINITY) { + return Double.POSITIVE_INFINITY; + } else if (targetPathWeight == Double.POSITIVE_INFINITY) { + return Double.NEGATIVE_INFINITY; + } else { + return BigDecimal + .valueOf(edgeWeight).subtract(BigDecimal.valueOf(targetPathWeight)) + .add(BigDecimal.valueOf(sourcePathWeight)).doubleValue(); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputation.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputation.java new file mode 100644 index 00000000000..f41228bcc94 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputation.java @@ -0,0 +1,1316 @@ +/* + * (C) Copyright 2020-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPath.java new file mode 100644 index 00000000000..12d9c5e1ec1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPath.java @@ -0,0 +1,309 @@ +/* + * (C) Copyright 2020-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeMeasurer.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeMeasurer.java new file mode 100644 index 00000000000..4e3638320bc --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeMeasurer.java @@ -0,0 +1,114 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsImpl.java new file mode 100644 index 00000000000..d1929160ef5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsImpl.java @@ -0,0 +1,162 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenKShortestPath.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenKShortestPath.java new file mode 100644 index 00000000000..3db1b7ebb53 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenKShortestPath.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2019-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenShortestPathIterator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenShortestPathIterator.java new file mode 100644 index 00000000000..83ff799567f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenShortestPathIterator.java @@ -0,0 +1,661 @@ +/* + * (C) Copyright 2019-2023, 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); + } + } + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/package-info.java new file mode 100644 index 00000000000..0a4f9b9144f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2017-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Shortest-path related algorithms. + */ +package org.jgrapht.alg.shortestpath; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistance.java b/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistance.java new file mode 100644 index 00000000000..a92675a6918 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistance.java @@ -0,0 +1,729 @@ +/* + * (C) Copyright 2020-2023, 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 is $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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/package-info.java new file mode 100644 index 00000000000..677027ae3cf --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, 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 + */ + +/** + * Algorithms for computing graph similarity metrics. + */ +package org.jgrapht.alg.similarity; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AbstractCapacitatedMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AbstractCapacitatedMinimumSpanningTree.java new file mode 100644 index 00000000000..51c7a4a8fb1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AbstractCapacitatedMinimumSpanningTree.java @@ -0,0 +1,437 @@ +/* + * (C) Copyright 2018-2023, 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 + * {@code labels} and {@code 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 {@code vertex} from the subset represented by {@code fromLabel} to the + * subset represented by {@code 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 {@code vertices} from the subset represented by + * {@code fromLabel} to the subset represented by {@code 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 {@code label} of the partition. + * + * @param vertexSubset the subset represented by {@code 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 {@code vertex}. + * + * @param vertex the vertex to return the label from + * + * @return the label of {@code 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 {@code 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 {@code 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 + * {@code 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 + * {@code 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) { + throw new RuntimeException(e); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTree.java new file mode 100644 index 00000000000..bb627919e55 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTree.java @@ -0,0 +1,1544 @@ +/* + * (C) Copyright 2018-2023, 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 {@code v} with respect to the MST given in + * {@code 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 {@code v} with respect to the MST given in + * {@code 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 {@code 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 {@code v1} and returns if the vertex + * {@code v1}. That is, all incident edges of {@code v1} are removed if + * {@code 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 {@code v1} + * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of + * the subtree rooted at {@code v1} + * @return true iff {@code 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 {@code v1} + * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of + * the subtree rooted at {@code 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 {@code vertexOfV1Single} to nodes in the subset represented + * by {@code 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 {@code 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 {@code vertexOfV1Single} to nodes in the subset represented + * by {@code label}. This method does adds the subtree of v1 to + * {@code 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 {@code 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 {@code v1} and {@code v2} to the improvement graph if + * {@code newCapacity} does not exceed the capacity constraint. The weight of the edge + * is {@code newCost}. + * + * @param v1 start vertex (the vertex or subtree induced by {@code v1} that will be + * moved to the subset of {@code v2}) + * @param v2 end vertex (the vertex or subtree induced by {@code v2} that will be + * removed from the subset of {@code v2}) + * @param newCapacity the used capacity by adding the vertex or subtree induced by + * {@code v1} to the subset of {@code v2} and deleting the vertex or + * subtree induced by {@code v2} + * @param newCost the cost of the edge (the cost induced by the operation induced by + * {@code v1} and {@code 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 + * {@code 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 {@code 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 + * {@code 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; + } + }; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTree.java new file mode 100644 index 00000000000..25115b160a5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTree.java @@ -0,0 +1,144 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTree.java new file mode 100644 index 00000000000..b10e5e9d4de --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTree.java @@ -0,0 +1,359 @@ +/* + * (C) Copyright 2018-2023, 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 {@code 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 {@code savings}. + * + * @param savings the savings calculated in the algorithm (see getSolution()) + * @return the list of the {@code 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 {@code vertex} such that the connection of + * {@code 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpanner.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpanner.java new file mode 100644 index 00000000000..b8ab771d451 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpanner.java @@ -0,0 +1,281 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTree.java new file mode 100644 index 00000000000..d9c3bf0cb41 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTree.java @@ -0,0 +1,79 @@ +/* + * (C) Copyright 2010-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTree.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTree.java new file mode 100644 index 00000000000..bb712abf4bd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTree.java @@ -0,0 +1,133 @@ +/* + * (C) Copyright 2013-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/package-info.java new file mode 100644 index 00000000000..5c29c8a45d4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Spanning tree and spanner algorithms. + */ +package org.jgrapht.alg.spanning; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithm.java new file mode 100644 index 00000000000..3a8ea58b9da --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithm.java @@ -0,0 +1,232 @@ +/* + * (C) Copyright 2025, by Lena Büttel and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ +package org.jgrapht.alg.steiner; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.GraphTests; +import org.jgrapht.Graphs; +import org.jgrapht.alg.interfaces.SteinerTreeAlgorithm; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.alg.spanning.KruskalMinimumSpanningTree; +import org.jgrapht.alg.util.Pair; +import org.jgrapht.graph.SimpleWeightedGraph; + +/** + * Implementation of the Kou-Markowsky-Berman algorithm for computing Steiner trees. + * + *

    + * The Kou-Markowsky-Berman algorithm is an approximation algorithm for the Steiner tree problem. It + * constructs a Steiner tree by first creating a complete distance graph on the Steiner points + * (terminals), computing a minimum spanning tree on this graph, replacing each edge with the + * corresponding shortest path in the original graph, and then optimizing the result. + * + *

    + * The algorithm runs in $O(|S||V|^2)$ time and it guarantees to output a tree that spans $S$ with + * total distance on its edges no more than $2 (1-\frac{1}{l})$ times that of the optimal tree, + * where $l$ is the number of leaves in the optimal tree. + * + * @param the graph vertex type + * @param the graph edge type + * + * @author Lena Büttel + * + * @see SteinerTreeAlgorithm + * @see Steiner tree problem + */ +public class KouMarkowskyBermanAlgorithm + implements + SteinerTreeAlgorithm +{ + + private final Graph graph; + + /** + * Construct a new instance of the algorithm. + * + * @param graph the input graph; must be connected with non-negative edge weights + * @throws NullPointerException if the graph is null + */ + public KouMarkowskyBermanAlgorithm(Graph graph) + { + this.graph = GraphTests.requireUndirected(graph); + } + + /** + * Computes a Steiner tree using the Kou-Markowsky-Berman algorithm. + * + *

    + * The algorithm finds a tree that connects all the specified Steiner points (terminals) with + * minimum total weight, potentially using intermediate vertices not in the terminal set. The + * result is guaranteed to be at most twice the weight of the optimal Steiner tree. + * + * @param steinerPoints the set of vertices (terminals) that must be connected by the Steiner + * tree; must not be null or empty + * @return a Steiner tree connecting all the specified vertices + * @throws IllegalArgumentException if steinerPoints is null or empty + * @throws RuntimeException if the graph is not connected or contains negative edge weights + */ + @Override + public SteinerTree getSteinerTree(Set steinerPoints) + { + if (steinerPoints == null || steinerPoints.isEmpty()) { + throw new IllegalArgumentException("Steiner points cannot be null or empty"); + } + + // Single vertex case + if (steinerPoints.size() == 1) { + return new SteinerTreeAlgorithm.SteinerTreeImpl<>(Set.of(), 0.0); + } + + // Step 1: Create the complete distance graph of selected vertices + DijkstraShortestPath dijkstraAlg = new DijkstraShortestPath<>(graph); + + Graph completeGraph = + new SimpleWeightedGraph<>(graph.getVertexSupplier(), graph.getEdgeSupplier()); + for (V vertex : steinerPoints) { + completeGraph.addVertex(vertex); + } + + // Store the actual paths for later reconstruction + Map, GraphPath> storePaths = new HashMap<>(); + List selectedList = new ArrayList<>(steinerPoints); + + // Compute shortest paths between all pairs of Steiner points + for (int i = 0; i < selectedList.size(); i++) { + for (int j = i + 1; j < selectedList.size(); j++) { + V source = selectedList.get(i); + V target = selectedList.get(j); + GraphPath path = dijkstraAlg.getPath(source, target); + double weight = path.getWeight(); + storePaths.put(Pair.of(source, target), path); + + E edge = completeGraph.addEdge(source, target); + completeGraph.setEdgeWeight(edge, weight); + } + } + + // Step 2: MST of complete distance graph + KruskalMinimumSpanningTree kruskal = new KruskalMinimumSpanningTree<>(completeGraph); + Graph mstGraph = + new SimpleWeightedGraph<>(graph.getVertexSupplier(), graph.getEdgeSupplier()); + + for (V vertex : completeGraph.vertexSet()) { + mstGraph.addVertex(vertex); + } + for (E edge : kruskal.getSpanningTree().getEdges()) { + V source = completeGraph.getEdgeSource(edge); + V target = completeGraph.getEdgeTarget(edge); + E newEdge = mstGraph.addEdge(source, target); + double edgeWeight = completeGraph.getEdgeWeight(edge); + mstGraph.setEdgeWeight(newEdge, edgeWeight); + } + + // Step 3: Replace MST edges with actual shortest paths + // This step reconstructs the full paths in the original graph + Graph mstPathGraph = + new SimpleWeightedGraph<>(graph.getVertexSupplier(), graph.getEdgeSupplier()); + + for (E edge : mstGraph.edgeSet()) { + V source = mstGraph.getEdgeSource(edge); + V target = mstGraph.getEdgeTarget(edge); + + GraphPath path = storePaths.get(Pair.of(source, target)); + if (path == null) + path = storePaths.get(Pair.of(target, source)); + if (path == null) + continue; + + List vertices = path.getVertexList(); + for (int i = 0; i < vertices.size() - 1; i++) { + V v1 = vertices.get(i); + V v2 = vertices.get(i + 1); + + mstPathGraph.addVertex(v1); + mstPathGraph.addVertex(v2); + + if (!mstPathGraph.containsEdge(v1, v2)) { + E originalEdge = graph.getEdge(v1, v2); + if (originalEdge != null) { + E newEdge = mstPathGraph.addEdge(v1, v2); + mstPathGraph.setEdgeWeight(newEdge, graph.getEdgeWeight(originalEdge)); + } + } + } + } + + // Step 4: Compute MST of the expanded graph + // This removes any redundant edges introduced by path expansion + KruskalMinimumSpanningTree kruskal1 = new KruskalMinimumSpanningTree<>(mstPathGraph); + Graph finalMST = + new SimpleWeightedGraph<>(graph.getVertexSupplier(), graph.getEdgeSupplier()); + + for (V vertex : mstPathGraph.vertexSet()) { + finalMST.addVertex(vertex); + } + for (E edge : kruskal1.getSpanningTree().getEdges()) { + V source = mstPathGraph.getEdgeSource(edge); + V target = mstPathGraph.getEdgeTarget(edge); + E newEdge = finalMST.addEdge(source, target); + finalMST.setEdgeWeight(newEdge, mstPathGraph.getEdgeWeight(edge)); + } + + // Step 5: Prune non-Steiner leaves + // Remove leaf vertices that are not required Steiner points + // This optimization step removes unnecessary vertices + ArrayDeque leavesQueue = new ArrayDeque<>(); + for (V v : finalMST.vertexSet()) { + if (finalMST.degreeOf(v) == 1 && !steinerPoints.contains(v)) { + leavesQueue.add(v); + } + } + + while (!leavesQueue.isEmpty()) { + V leaf = leavesQueue.poll(); + if (!finalMST.containsVertex(leaf)) { + continue; // already removed + } + if (steinerPoints.contains(leaf) || finalMST.degreeOf(leaf) != 1) { + continue; // no longer a removable leaf + } + + // check the neighbor(s) for new leaf status + List neighbors = Graphs.neighborListOf(finalMST, leaf); + finalMST.removeVertex(leaf); + + for (V neighbor : neighbors) { + if (finalMST.containsVertex(neighbor) && finalMST.degreeOf(neighbor) == 1 + && !steinerPoints.contains(neighbor)) + { + leavesQueue.add(neighbor); + } + } + } + + double totalWeight = finalMST.edgeSet().stream().mapToDouble(finalMST::getEdgeWeight).sum(); + + return new SteinerTreeAlgorithm.SteinerTreeImpl<>(finalMST.edgeSet(), totalWeight); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/package-info.java new file mode 100644 index 00000000000..4ede90dc80f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/steiner/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2017-2025, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Steiner tree related algorithms. + */ +package org.jgrapht.alg.steiner; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSP.java new file mode 100644 index 00000000000..5e25800edc0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSP.java @@ -0,0 +1,135 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSP.java new file mode 100644 index 00000000000..ec1417074fa --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSP.java @@ -0,0 +1,319 @@ +/* + * (C) Copyright 2021-2024, by J. Alejandro Cornejo-Acosta and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.Graphs; +import org.jgrapht.graph.GraphWalk; +import org.jgrapht.util.ArrayUtil; +import org.jgrapht.util.VertexToIntegerMapping; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * The farthest 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 partial tour of a subset of all cities, + * and then iteratively an unvisited vertex (a vertex whichis not in the tour) is chosen given a criterion + * and inserted in the best position of the partial tour. Per each iteration, the farthest insertion + * heuristic selects the farthest unvisited vertex from the partial tour. + * This algorithm provides a guarantee to compute tours no more than + * 0(log N) times optimum (assuming the triangle inequality). However, regarding practical results, + * some references refer to this heuristic as one of the best among the category of insertion heuristics. + * This implementation uses the longest edge by default as the initial sub-tour if one is not provided. + *

    + * + *

    + * The description of this algorithm can be consulted on:
    + * Johnson, D. S., & McGeoch, L. A. (2007). Experimental Analysis of Heuristics for the STSP. + * In G. Gutin & A. P. Punnen (Eds.), The Traveling Salesman Problem and Its Variations (pp. 369–443). + * Springer US. https://doi.org/10.1007/0-306-48213-4_9 + *

    + * + *

    + * This implementation can also be used in order to augment an existing partial tour. See + * constructor {@link #FarthestInsertionHeuristicTSP(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 J. Alejandro Cornejo-Acosta + */ +public class FarthestInsertionHeuristicTSP + extends + HamiltonianCycleAlgorithmBase +{ + + /** + * Initial vertices in the tour + */ + private GraphPath initialSubtour; + + /** + * Distances from unvisited vertices to the partially constructed tour + */ + private double[] distances = null; + + /** + * Matrix of distances between all vertices + */ + private double[][] allDist; + + /** + * Mapping of vertices to integers to work on. + */ + private VertexToIntegerMapping mapping; + + + /** + * Constructor. By default a sub-tour is chosen based on the longest edge + */ + public FarthestInsertionHeuristicTSP() + { + 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 longest edge + */ + public FarthestInsertionHeuristicTSP(GraphPath subtour) + { + this.initialSubtour = subtour; + } + + // algorithm + + /** + * Computes a tour using the farthest 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 + */ + @Override + public GraphPath getTour(Graph graph) + { + checkGraph(graph); + if (graph.vertexSet().size() == 1) { + return getSingletonTour(graph); + } + + mapping = Graphs.getVertexToIntegerMapping(graph); + + + // Computes matrix of distances + E longesEdge = computeDistanceMatrix(graph); + if (initialSubtour == null || initialSubtour.getVertexList().isEmpty()) { + // If no initial subtour was provided, create one based on the longest edge + V v = graph.getEdgeSource(longesEdge); + V u = graph.getEdgeTarget(longesEdge); + + // at this point weight does not matter + initialSubtour = new GraphWalk<>(graph, List.of(v, u), -1); + } + + int n = mapping.getIndexList().size(); + + // initialize tour + int[] tour = initPartialTour(); + + // init distances from unvisited vertices to the partially constructed tour + initDistances(tour); + + // construct tour + for (int i = initialSubtour.getVertexList().size(); i < n; i++) { + + // Find the index of the farthest unvisited vertex. + int idxFarthest = getFarthest(i); + int k = tour[idxFarthest]; + + // Search for the best position of vertex k in the tour + double saving = Double.POSITIVE_INFINITY; + int bestIndex = -1; + for (int j = 0; j <= i; j++) { + + int x = (j == 0 ? tour[i - 1] : tour[j - 1]); + int y = (j == i ? tour[0] : tour[j]); + + double dxk = allDist[x][k]; + double dky = allDist[k][y]; + double dxy = (x == y ? 0 : allDist[x][y]); + + double savingTmp = dxk + dky - dxy; + if (savingTmp < saving) { + saving = savingTmp; + bestIndex = j; + } + } + ArrayUtil.swap(tour, i, idxFarthest); + ArrayUtil.swap(distances, i, idxFarthest); + + // perform insertion of vertex k + for (int j = i; j > bestIndex; j--) { + tour[j] = tour[j - 1]; + } + tour[bestIndex] = k; + + // Update distances from vertices to the partial tour + updateDistances(k, i + 1); + } + + tour[n] = tour[0]; // close tour manually. Arrays.asList does not support add + + // Map the tour from integer values to V values + List tourList = Arrays.stream(tour).mapToObj(i -> mapping.getIndexList().get(i)) + .collect(Collectors.toList()); + return closedVertexListToTour(tourList, graph); + } + + /** + * Initialize the partial tour with the vertices of {@code initialSubtour} at the beginning of + * the tour. + * + * @return a dummy tour with the vertices of {@code initialSubtour} at the beginning. + */ + private int[] initPartialTour() + { + int n = mapping.getVertexMap().size(); + int[] tour = new int[n + 1]; + Set visited = new HashSet<>(); + int i = 0; + for (var v : initialSubtour.getVertexList()) { + int iv = mapping.getVertexMap().get(v); + visited.add(iv); + tour[i++] = iv; + } + for (int v = 0; v < n; v++) { + if (!visited.contains(v)) { + tour[i++] = v; + } + } + + return tour; + } + + + /** + * Computes the matrix of distances by using the already computed {@code mapping} + * of vertices to integers + * + * @param graph the input graph + * @return the longest edge to initialize the partial tour if necessary + */ + private E computeDistanceMatrix(Graph graph) + { + E longestEdge = null; + double longestEdgeWeight = -1; + int n = graph.vertexSet().size(); + allDist = new double[n][n]; + for (var edge : graph.edgeSet()) { + V source = graph.getEdgeSource(edge); + V target = graph.getEdgeTarget(edge); + if (!source.equals(target)) { + int i = mapping.getVertexMap().get(source); + int j = mapping.getVertexMap().get(target); + if (allDist[i][j] == 0) { + allDist[i][j] = allDist[j][i] = graph.getEdgeWeight(edge); + if (longestEdgeWeight < allDist[i][j]) { + longestEdgeWeight = allDist[i][j]; + longestEdge = edge; + } + } + } + } + return longestEdge; + } + + + /** + * Find the index of the unvisited vertex which is farthest from the partially constructed tour. + * + * @param start The unvisited vertices start at index {@code start} + * @return the index of the unvisited vertex which is farthest from the partially constructed tour. + */ + private int getFarthest(int start) + { + int n = distances.length; + int farthest = -1; + double maxDist = -1; + for (int i = start; i < n; i++) { + double dist = distances[i]; + if (dist > maxDist) { + farthest = i; + maxDist = dist; + } + } + return farthest; + } + + /** + * Initialize distances from the unvisited vertices to the initial subtour + * + * @param tour a partial tour with {@code initialSubtour} at the beginning + */ + private void initDistances(int[] tour) + { + int n = mapping.getVertexMap().size(); + int start = initialSubtour.getVertexList().size(); + distances = new double[n]; + Arrays.fill(distances, start, n, Double.POSITIVE_INFINITY); + for (int i = start; i < n; i++) { + for (int j = 0; j < start; j++) { + distances[i] = Math.min(distances[i], allDist[tour[i]][tour[j]]); + } + } + } + + /** + * Update the distances from the unvisited vertices to the partially constructed tour + * + * @param v the last vertex added to the tour + * @param start the unvisited vertices start at index {@code start} + */ + private void updateDistances(int v, int start) + { + for (int i = start; i < distances.length; i++) { + distances[i] = Math.min(allDist[v][i], distances[i]); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/GreedyHeuristicTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/GreedyHeuristicTSP.java new file mode 100644 index 00000000000..0f8ee3dea35 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/GreedyHeuristicTSP.java @@ -0,0 +1,136 @@ +/* + * (C) Copyright 2019-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HamiltonianCycleAlgorithmBase.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HamiltonianCycleAlgorithmBase.java new file mode 100644 index 00000000000..7417a9dba84 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HamiltonianCycleAlgorithmBase.java @@ -0,0 +1,144 @@ +/* + * (C) Copyright 2019-2023, 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"); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HeldKarpTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HeldKarpTSP.java new file mode 100644 index 00000000000..52201f4135a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HeldKarpTSP.java @@ -0,0 +1,196 @@ +/* + * (C) Copyright 2017-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestInsertionHeuristicTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestInsertionHeuristicTSP.java new file mode 100644 index 00000000000..31ec06d05fa --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestInsertionHeuristicTSP.java @@ -0,0 +1,338 @@ +/* + * (C) Copyright 2019-2023, 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); + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSP.java new file mode 100644 index 00000000000..5762dc943f6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSP.java @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2019-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/PalmerHamiltonianCycle.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/PalmerHamiltonianCycle.java new file mode 100644 index 00000000000..947f9ed118a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/PalmerHamiltonianCycle.java @@ -0,0 +1,148 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/RandomTourTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/RandomTourTSP.java new file mode 100644 index 00000000000..301d167ffd4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/RandomTourTSP.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoApproxMetricTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoApproxMetricTSP.java new file mode 100644 index 00000000000..abf32db173e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoApproxMetricTSP.java @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoOptHeuristicTSP.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoOptHeuristicTSP.java new file mode 100644 index 00000000000..89615bcfc45 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoOptHeuristicTSP.java @@ -0,0 +1,332 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/tour/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/package-info.java new file mode 100644 index 00000000000..1b74806de8c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/tour/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2017-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph tours related algorithms. + */ +package org.jgrapht.alg.tour; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/transform/LineGraphConverter.java b/jgrapht-core/src/main/java/org/jgrapht/alg/transform/LineGraphConverter.java new file mode 100644 index 00000000000..23326fa119f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/transform/LineGraphConverter.java @@ -0,0 +1,129 @@ +/* + * (C) Copyright 2018-2023, 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)); + } + } + } + } + + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/transform/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/transform/package-info.java new file mode 100644 index 00000000000..5fe83db614d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/transform/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 for graph transformers + */ +package org.jgrapht.alg.transform; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/AliasMethodSampler.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/AliasMethodSampler.java new file mode 100644 index 00000000000..1311185a9f4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/AliasMethodSampler.java @@ -0,0 +1,173 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 if an invalid probability distribution is supplied + */ + 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 + * + * @throws IllegalArgumentException if an invalid probability distribution is supplied + * @throws NullPointerException if {@code rng} is {@code null} + */ + 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 if an invalid probability distribution is supplied + * @throws NullPointerException if {@code rng} is {@code null} + */ + 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 if an invalid probability distribution is supplied + * @throws NullPointerException if {@code rng} is {@code null} + */ + 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]; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/FixedSizeIntegerQueue.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/FixedSizeIntegerQueue.java new file mode 100644 index 00000000000..f80d521b8cb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/FixedSizeIntegerQueue.java @@ -0,0 +1,110 @@ +/* + * (C) Copyright 2017-2023, 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, {@link #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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/NeighborCache.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/NeighborCache.java new file mode 100644 index 00000000000..86c7fb6bbbc --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/NeighborCache.java @@ -0,0 +1,262 @@ +/* + * (C) Copyright 2017-2023, 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 {@code 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(); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/Pair.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/Pair.java new file mode 100644 index 00000000000..cbab4b20667 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/Pair.java @@ -0,0 +1,156 @@ +/* + * (C) Copyright 2015-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/ToleranceDoubleComparator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/ToleranceDoubleComparator.java new file mode 100644 index 00000000000..6d8f2dac902 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/ToleranceDoubleComparator.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 + * + * @throws NullPointerException if either one of the arguments is {@code null} + */ + @Override + public int compare(Double o1, Double o2) + { + if (Math.abs(o1 - o2) < epsilon) { + return 0; + } else { + return Double.compare(o1, o2); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/Triple.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/Triple.java new file mode 100644 index 00000000000..1cb77c1735e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/Triple.java @@ -0,0 +1,183 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnionFind.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnionFind.java index dcdb8aac4a5..118119f7329 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnionFind.java +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnionFind.java @@ -1,83 +1,59 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. +/* + * (C) Copyright 2010-2023, by Tom Conerly and Contributors. * - * 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. - */ -/* ------------------------- - * UnionFind.java - * ------------------------- - * (C) Copyright 2010-2010, by Tom Conerly and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Tom Conerly - * Contributor(s): + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 02-Feb-2010 : Initial revision (TC); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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(a(n)) per operation where a is the inverse - * Ackermann function. UnionFind uses the hashCode and equals method of the + * 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 - * @since Feb 10, 2010 */ public class UnionFind { - //~ Instance fields -------------------------------------------------------- - - private Map parentMap; - private Map rankMap; - - //~ Constructors ----------------------------------------------------------- + private final Map parentMap; + private final Map rankMap; + private int count; // number of components /** - * Creates a UnionFind instance with all of the elements of elements in - * seperate sets. + * 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 HashMap(); - rankMap = new HashMap(); + parentMap = new LinkedHashMap<>(); + rankMap = new HashMap<>(); for (T element : elements) { parentMap.put(element, element); rankMap.put(element, 0); } + count = elements.size(); } - //~ Methods ---------------------------------------------------------------- - /** * Adds a new element to the data structure in its own set. * @@ -85,8 +61,12 @@ public UnionFind(Set elements) */ 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++; } /** @@ -112,42 +92,51 @@ protected Map getRankMap() * * @return The element representing the set the element is in. */ - public T find(T element) + public T find(final T element) { if (!parentMap.containsKey(element)) { throw new IllegalArgumentException( - "elements must be contained in given set"); + "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; - T parent = parentMap.get(element); - if (parent.equals(element)) { - return element; + current = element; + while (!current.equals(root)) { + T parent = parentMap.get(current); + parentMap.put(current, root); + current = parent; } - T newParent = find(parent); - parentMap.put(element, newParent); - return newParent; + return root; } /** - * Merges the sets which contain element1 and element2. + * 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"); + 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 + // check if the elements are already in the same set if (parent1.equals(parent2)) { return; } @@ -162,7 +151,76 @@ public void union(T element1, T element2) 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)); } -} -// End UnionFind.java + /** + * 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(", ", "{", "}")); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnorderedPair.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnorderedPair.java new file mode 100644 index 00000000000..bcc94441df2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnorderedPair.java @@ -0,0 +1,95 @@ +/* + * (C) Copyright 2003-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/VertexDegreeComparator.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/VertexDegreeComparator.java index a09d6150c1e..902ad2f1915 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/util/VertexDegreeComparator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/VertexDegreeComparator.java @@ -1,134 +1,134 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------------- - * VertexDegreeComparator.java - * --------------------------- - * (C) Copyright 2003-2008, by Linda Buisman and Contributors. +/* + * (C) Copyright 2003-2023, by Linda Buisman and Contributors. * - * Original Author: Linda Buisman - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 06-Nov-2003 : Initial revision (LB); - * 07-Jun-2005 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.

    + *

    + * 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 - * @since Nov 6, 2003 + * */ public class VertexDegreeComparator - implements java.util.Comparator + implements Comparator { - //~ Instance fields -------------------------------------------------------- + /** + * 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) /** - * The graph that contains the vertices to be compared. + * Order in which we sort the vertices: ascending vertex degree or descending vertex degree + * + * @deprecated use {@link VertexDegreeComparator#of(Graph)} */ - private UndirectedGraph graph; + @Deprecated(forRemoval = true, since = "1.5.1") + public enum Order + { + ASCENDING, + DESCENDING + } /** - * The sort order for vertex degree. true for ascending degree - * order (smaller degrees first), false for descending. + * The graph that contains the vertices to be compared. */ - private boolean ascendingOrder; + private Graph graph; - //~ Constructors ----------------------------------------------------------- + /** + * 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). + * 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)} */ - public VertexDegreeComparator(UndirectedGraph g) + @Deprecated(forRemoval = true, since = "1.5.1") + public VertexDegreeComparator(Graph g) { - this(g, true); + this(g, Order.ASCENDING); } /** - * Creates a comparator for comparing the degrees of vertices in the - * specified graph. + * 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 ascendingOrder true - compares in ascending order of degrees - * (lowest first), false - compares in descending order of degrees (highest - * first). + * @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. */ - public VertexDegreeComparator( - UndirectedGraph g, - boolean ascendingOrder) + @Deprecated(forRemoval = true, since = "1.5.1") + public VertexDegreeComparator(Graph g, Order order) { graph = g; - this.ascendingOrder = ascendingOrder; + this.order = order; } - //~ Methods ---------------------------------------------------------------- - /** - * Compare the degrees of v1 and v2, taking into - * account whether ascending or descending order is used. + * Compare the degrees of {@code v1} and {@code 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. + * @return -1 if {@code v1} comes before {@code v2}, +1 if {@code v1} + * comes after {@code 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 degree1 = graph.degreeOf(v1); - int degree2 = graph.degreeOf(v2); + int comparison = Integer.compare(graph.degreeOf(v1), graph.degreeOf(v2)); - if (((degree1 < degree2) && ascendingOrder) - || ((degree1 > degree2) && !ascendingOrder)) - { - return -1; - } else if ( - ((degree1 > degree2) && ascendingOrder) - || ((degree1 < degree2) && !ascendingOrder)) - { - return 1; + if (order == Order.ASCENDING) { + return comparison; } else { - return 0; + return -1 * comparison; } } } - -// End VertexDegreeComparator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/Extension.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/Extension.java new file mode 100644 index 00000000000..e1e6e7663ea --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/Extension.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2023, 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 +{ + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionFactory.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionFactory.java new file mode 100644 index 00000000000..58ca500e69d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionFactory.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2015-2023, 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(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionManager.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionManager.java new file mode 100644 index 00000000000..ea7ba00e75a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/ExtensionManager.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2015-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/package-info.java new file mode 100644 index 00000000000..bb1e0e30509 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Utility classes for managing extensions/encapsulations. + */ +package org.jgrapht.alg.util.extension; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/util/package-info.java new file mode 100644 index 00000000000..f53f1892fa9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/util/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Utilities used by JGraphT algorithms. + */ +package org.jgrapht.alg.util; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/util/package.html b/jgrapht-core/src/main/java/org/jgrapht/alg/util/package.html deleted file mode 100644 index 38c56f61be8..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/alg/util/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Utilities used by JGraphT algorithms. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImpl.java new file mode 100644 index 00000000000..c85df7522a1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImpl.java @@ -0,0 +1,114 @@ +/* + * (C) Copyright 2016-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImpl.java new file mode 100644 index 00000000000..d9bbacb15a3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImpl.java @@ -0,0 +1,144 @@ +/* + * (C) Copyright 2016-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImpl.java new file mode 100644 index 00000000000..37b19588e1f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImpl.java @@ -0,0 +1,105 @@ +/* + * (C) Copyright 2003-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/GreedyVCImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/GreedyVCImpl.java new file mode 100644 index 00000000000..d440a482397 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/GreedyVCImpl.java @@ -0,0 +1,154 @@ +/* + * (C) Copyright 2016-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImpl.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImpl.java new file mode 100644 index 00000000000..1872f6a78e9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImpl.java @@ -0,0 +1,340 @@ +/* + * (C) Copyright 2003-2023, 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; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/package-info.java new file mode 100644 index 00000000000..5b78522b4fd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Vertex cover algorithms. + */ +package org.jgrapht.alg.vertexcover; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/RatioVertex.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/RatioVertex.java new file mode 100644 index 00000000000..6340a3989ed --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/RatioVertex.java @@ -0,0 +1,154 @@ +/* + * (C) Copyright 2016-2023, 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 + * + * @deprecated use {@link #id} instead + **/ + @Deprecated(since = "1.5.2", forRemoval = true) + public final int ID; // @CS.suppress[MemberName] + + /** 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 = 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 + ")"; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/package-info.java new file mode 100644 index 00000000000..5198bdf8426 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Utilities for vertex cover algorithms. + */ +package org.jgrapht.alg.vertexcover.util; diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/ConnectedComponentTraversalEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/ConnectedComponentTraversalEvent.java index 134be609942..3c61f077c83 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/ConnectedComponentTraversalEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/ConnectedComponentTraversalEvent.java @@ -1,59 +1,32 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------------- - * ConnectedComponentTraversalEvent.java - * ------------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id: ConnectedComponentTraversalEvent.java 487 2006-07-02 00:53:17Z - * perfecthash $ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 11-Aug-2003 : Initial revision (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 - * @since Aug 11, 2003 */ public class ConnectedComponentTraversalEvent extends EventObject { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3834311717709822262L; /** @@ -66,15 +39,11 @@ public class ConnectedComponentTraversalEvent */ public static final int CONNECTED_COMPONENT_FINISHED = 32; - //~ Instance fields -------------------------------------------------------- - /** * The type of this event. */ private int type; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new ConnectedComponentTraversalEvent. * @@ -87,8 +56,6 @@ public ConnectedComponentTraversalEvent(Object eventSource, int type) this.type = type; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the event type. * @@ -99,5 +66,3 @@ public int getType() return type; } } - -// End ConnectedComponentTraversalEvent.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/EdgeTraversalEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/EdgeTraversalEvent.java index 4cd33edd5db..b6e06c0ad80 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/EdgeTraversalEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/EdgeTraversalEvent.java @@ -1,70 +1,41 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ----------------------- - * EdgeTraversalEvent.java - * ----------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 11-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 - * @since Aug 11, 2003 */ -public class EdgeTraversalEvent +public class EdgeTraversalEvent extends EventObject { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 4050768173789820979L; - //~ Instance fields -------------------------------------------------------- - /** * The traversed edge. */ protected E edge; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new EdgeTraversalEvent. * @@ -77,8 +48,6 @@ public EdgeTraversalEvent(Object eventSource, E edge) this.edge = edge; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the traversed edge. * @@ -89,5 +58,3 @@ public E getEdge() return edge; } } - -// End EdgeTraversalEvent.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/GraphChangeEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/GraphChangeEvent.java index 7b0a6a9f385..1669326fbc4 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/GraphChangeEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/GraphChangeEvent.java @@ -1,70 +1,39 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------- - * GraphChangeEvent.java - * --------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Aug-2003 : Initial revision (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * An event which indicates that a graph has changed. This class is a root for graph change events. * * @author Barak Naveh - * @since Aug 10, 2003 */ public class GraphChangeEvent extends EventObject { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3834592106026382391L; - //~ Instance fields -------------------------------------------------------- - /** * The type of graph change this event indicates. */ protected int type; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new graph change event. * @@ -77,8 +46,6 @@ public GraphChangeEvent(Object eventSource, int type) this.type = type; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the event type. * @@ -89,5 +56,3 @@ public int getType() return type; } } - -// End GraphChangeEvent.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/GraphEdgeChangeEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/GraphEdgeChangeEvent.java index bfe47c0b741..23d36fe046a 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/GraphEdgeChangeEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/GraphEdgeChangeEvent.java @@ -1,71 +1,47 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * GraphEdgeChangeEvent.java - * ------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 - * @since Aug 10, 2003 */ public class GraphEdgeChangeEvent extends GraphChangeEvent { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3618134563335844662L; + private static final long serialVersionUID = -4421610303769803253L; /** - * Before edge added event. This event is fired before an edge is added to a - * graph. + * 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. + * Before edge removed event. This event is fired before an edge is removed from a graph. */ public static final int BEFORE_EDGE_REMOVED = 22; @@ -75,12 +51,14 @@ public class GraphEdgeChangeEvent public static final int EDGE_ADDED = 23; /** - * Edge removed event. This event is fired after an edge is removed from a - * graph. + * Edge removed event. This event is fired after an edge is removed from a graph. */ public static final int EDGE_REMOVED = 24; - //~ Instance fields -------------------------------------------------------- + /** + * 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. @@ -97,7 +75,10 @@ public class GraphEdgeChangeEvent */ protected V edgeTarget; - //~ Constructors ----------------------------------------------------------- + /** + * The weight of the edge that this event is related to. + */ + protected double edgeWeight; /** * Constructor for GraphEdgeChangeEvent. @@ -105,13 +86,12 @@ public class 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. - * - * @deprecated Use new constructor which takes vertex parameters. + * @param edgeSource edge source vertex + * @param edgeTarget edge target vertex */ - public GraphEdgeChangeEvent( - Object eventSource, int type, E edge) + public GraphEdgeChangeEvent(Object eventSource, int type, E edge, V edgeSource, V edgeTarget) { - this(eventSource, type, edge, null, null); + this(eventSource, type, edge, edgeSource, edgeTarget, Graph.DEFAULT_EDGE_WEIGHT); } /** @@ -122,19 +102,18 @@ public GraphEdgeChangeEvent( * @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) + 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; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the edge that this event is related to. * @@ -164,6 +143,15 @@ public V getEdgeTarget() { return edgeTarget; } -} -// End GraphEdgeChangeEvent.java + /** + * Returns the weight of the edge that this event is related to. + * + * @return event edge weight + */ + public double getEdgeWeight() + { + return edgeWeight; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/GraphListener.java b/jgrapht-core/src/main/java/org/jgrapht/event/GraphListener.java index fb185c048df..f673d16c8ee 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/GraphListener.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/GraphListener.java @@ -1,74 +1,60 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ------------------ - * GraphListener.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.

    + *

    + * 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 - * @since Jul 18, 2003 */ public interface GraphListener extends VertexSetListener { - //~ Methods ---------------------------------------------------------------- - /** * Notifies that an edge has been added to the graph. * * @param e the edge event. */ - public void edgeAdded(GraphEdgeChangeEvent e); + void edgeAdded(GraphEdgeChangeEvent e); /** * Notifies that an edge has been removed from the graph. * * @param e the edge event. */ - public void edgeRemoved(GraphEdgeChangeEvent e); -} + void edgeRemoved(GraphEdgeChangeEvent e); -// End GraphListener.java + /** + * Notifies that an edge weight has been updated. + * + * @param e the edge event. + */ + default void edgeWeightUpdated(GraphEdgeChangeEvent e) + { + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/GraphVertexChangeEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/GraphVertexChangeEvent.java index 7c7dd11e413..919249eb353 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/GraphVertexChangeEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/GraphVertexChangeEvent.java @@ -1,95 +1,62 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------------- - * GraphVertexChangeEvent.java - * --------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 - * @since Aug 10, 2003 */ public class GraphVertexChangeEvent extends GraphChangeEvent { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3690189962679104053L; /** - * Before vertex added event. This event is fired before a vertex is added - * to a graph. + * 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. + * 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. + * 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. + * Vertex removed event. This event is fired after a vertex is removed from a graph. */ public static final int VERTEX_REMOVED = 14; - //~ Instance fields -------------------------------------------------------- - /** * The vertex that this event is related to. */ protected V vertex; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new GraphVertexChangeEvent object. * @@ -103,8 +70,6 @@ public GraphVertexChangeEvent(Object eventSource, int type, V vertex) this.vertex = vertex; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the vertex that this event is related to. * @@ -115,5 +80,3 @@ public V getVertex() return vertex; } } - -// End GraphVertexChangeEvent.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListener.java b/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListener.java index 605848e4716..6200179d450 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListener.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListener.java @@ -1,100 +1,71 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * TraversalListener.java - * ---------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Aug-2003 : Adaptation to new event model (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.EventListener; + /** * A listener on graph iterator or on a graph traverser. * + * @param the graph vertex type + * @param the graph edge type + * * @author Barak Naveh - * @since Jul 19, 2003 */ -public interface TraversalListener +public interface TraversalListener extends EventListener { - //~ Methods ---------------------------------------------------------------- - /** - * Called to inform listeners that the traversal of the current connected - * component has finished. + * Called to inform listeners that the traversal of the current connected component has + * finished. * * @param e the traversal event. */ - public void connectedComponentFinished( - ConnectedComponentTraversalEvent e); + void connectedComponentFinished(ConnectedComponentTraversalEvent e); /** - * Called to inform listeners that a traversal of a new connected component - * has started. + * Called to inform listeners that a traversal of a new connected component has started. * * @param e the traversal event. */ - public void connectedComponentStarted(ConnectedComponentTraversalEvent e); + 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. + * 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. */ - public void edgeTraversed(EdgeTraversalEvent e); + 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. + * 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. */ - public void vertexTraversed(VertexTraversalEvent e); + 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. + * 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. */ - public void vertexFinished(VertexTraversalEvent e); + void vertexFinished(VertexTraversalEvent e); } - -// End TraversalListener.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListenerAdapter.java b/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListenerAdapter.java index a4985e03b69..9c598f54ba9 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListenerAdapter.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListenerAdapter.java @@ -1,69 +1,46 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ----------------------------- - * TraversalListenerAdapter.java - * ----------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 06-Aug-2003 : Initial revision (BN); - * 11-Aug-2003 : Adaptation to new event model (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 - * @since Aug 6, 2003 */ public class TraversalListenerAdapter implements TraversalListener { - //~ Methods ---------------------------------------------------------------- - /** * @see TraversalListener#connectedComponentFinished(ConnectedComponentTraversalEvent) */ - public void connectedComponentFinished( - ConnectedComponentTraversalEvent e) + @Override + public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { } /** * @see TraversalListener#connectedComponentStarted(ConnectedComponentTraversalEvent) */ + @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { } @@ -71,13 +48,15 @@ public void connectedComponentStarted(ConnectedComponentTraversalEvent e) /** * @see TraversalListener#edgeTraversed(EdgeTraversalEvent) */ - public void edgeTraversed(EdgeTraversalEvent e) + @Override + public void edgeTraversed(EdgeTraversalEvent e) { } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ + @Override public void vertexTraversed(VertexTraversalEvent e) { } @@ -85,9 +64,8 @@ public void vertexTraversed(VertexTraversalEvent e) /** * @see TraversalListener#vertexFinished(VertexTraversalEvent) */ + @Override public void vertexFinished(VertexTraversalEvent e) { } } - -// End TraversalListenerAdapter.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/VertexSetListener.java b/jgrapht-core/src/main/java/org/jgrapht/event/VertexSetListener.java index c0dd69291a6..f33d4fc9be6 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/VertexSetListener.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/VertexSetListener.java @@ -1,77 +1,48 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * VertexSetListener.java - * ---------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 {@code GraphListener}. + * + * @param the graph vertex type * * @author Barak Naveh * @see GraphListener - * @since Jul 18, 2003 */ public interface VertexSetListener extends EventListener { - //~ Methods ---------------------------------------------------------------- - /** * Notifies that a vertex has been added to the graph. * * @param e the vertex event. */ - public void vertexAdded(GraphVertexChangeEvent e); + void vertexAdded(GraphVertexChangeEvent e); /** * Notifies that a vertex has been removed from the graph. * * @param e the vertex event. */ - public void vertexRemoved(GraphVertexChangeEvent e); + void vertexRemoved(GraphVertexChangeEvent e); } - -// End VertexSetListener.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/VertexTraversalEvent.java b/jgrapht-core/src/main/java/org/jgrapht/event/VertexTraversalEvent.java index c72e9274056..53313788518 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/event/VertexTraversalEvent.java +++ b/jgrapht-core/src/main/java/org/jgrapht/event/VertexTraversalEvent.java @@ -1,70 +1,41 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ------------------------- - * VertexTraversalEvent.java - * ------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 11-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 - * @since Aug 11, 2003 */ public class VertexTraversalEvent extends EventObject { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3688790267213918768L; - //~ Instance fields -------------------------------------------------------- - /** * The traversed vertex. */ protected V vertex; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new VertexTraversalEvent. * @@ -77,8 +48,6 @@ public VertexTraversalEvent(Object eventSource, V vertex) this.vertex = vertex; } - //~ Methods ---------------------------------------------------------------- - /** * Returns the traversed vertex. * @@ -89,5 +58,3 @@ public V getVertex() return vertex; } } - -// End VertexTraversalEvent.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/event/package-info.java new file mode 100644 index 00000000000..15448a691bb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/event/package-info.java @@ -0,0 +1,23 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Event classes and listener interfaces, used to provide a change notification mechanism on graph + * modification events. + */ +package org.jgrapht.event; diff --git a/jgrapht-core/src/main/java/org/jgrapht/event/package.html b/jgrapht-core/src/main/java/org/jgrapht/event/package.html deleted file mode 100644 index 9a0bf0962fe..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/event/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -Event classes and listener interfaces, used to provide a change -notification mechanism on graph modification events. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphReader.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphReader.java deleted file mode 100644 index 0bee22e9746..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphReader.java +++ /dev/null @@ -1,181 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * GraphReader.java - * ------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (BN); - * - */ -package org.jgrapht.experimental; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; - - -public class GraphReader - implements GraphGenerator -{ - //~ Instance fields -------------------------------------------------------- - - // ~ Static fields/initializers -------------------------------------------- - - // ~ Instance fields ------------------------------------------------------- - - // ~ Static fields/initializers -------------------------------------------- - - // ~ Instance fields ------------------------------------------------------- - - private final BufferedReader _in; - private final boolean _isWeighted; - private final double _defaultWeight; - - // ~ Constructors ---------------------------------------------------------- - - //~ Constructors ----------------------------------------------------------- - - /** - * Construct a new GraphReader. - */ - private GraphReader(Reader input, boolean isWeighted, double defaultWeight) - throws IOException - { - if (input instanceof BufferedReader) { - _in = (BufferedReader) input; - } else { - _in = new BufferedReader(input); - } - _isWeighted = isWeighted; - _defaultWeight = defaultWeight; - } - - /** - * Construct a new GraphReader. - */ - public GraphReader(Reader input) - throws IOException - { - this(input, false, 1); - } - - /** - * Construct a new GraphReader. - */ - public GraphReader(Reader input, double defaultWeight) - throws IOException - { - this(input, true, defaultWeight); - } - - //~ Methods ---------------------------------------------------------------- - - // ~ Methods --------------------------------------------------------------- - - private String [] split(final String src) - { - if (src == null) { - return null; - } - return src.split("\\s+"); - } - - private String [] skipComments() - { - String [] cols = null; - try { - cols = split(_in.readLine()); - while ( - (cols != null) - && ((cols.length == 0) - || cols[0].equals("c") - || cols[0].startsWith("%"))) - { - cols = split(_in.readLine()); - } - } catch (IOException e) { - } - return cols; - } - - private int readNodeCount() - { - final String [] cols = skipComments(); - if (cols[0].equals("p")) { - return Integer.parseInt(cols[1]); - } - return -1; - } - - /** - * {@inheritDoc} - */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) - { - final int size = readNodeCount(); - if (resultMap == null) { - resultMap = new HashMap(); - } - - for (int i = 0; i < size; i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); - resultMap.put(Integer.toString(i + 1), newVertex); - } - String [] cols = skipComments(); - while (cols != null) { - if (cols[0].equals("e")) { - E edge = - target.addEdge( - resultMap.get(cols[1]), - resultMap.get(cols[2])); - if (_isWeighted && (edge != null)) { - double weight = _defaultWeight; - if (cols.length > 3) { - weight = Double.parseDouble(cols[3]); - } - ((WeightedGraph) target).setEdgeWeight(edge, weight); - } - } - cols = skipComments(); - } - } -} - -// End GraphReader.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphSquare.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphSquare.java deleted file mode 100644 index 4d10c7376e7..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphSquare.java +++ /dev/null @@ -1,219 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * GraphSquare.java - * ---------------------- - * (C) Copyright 2004-2008, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 14-Sep-2004 : Initial revision (MB); - * - */ -package org.jgrapht.experimental; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.event.*; -import org.jgrapht.graph.*; - - -/** - * DOCUMENT ME! - * - * @author Michael Behrisch - * @since Sep 14, 2004 - */ -public class GraphSquare - extends AbstractBaseGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = -2642034600395594304L; - private static final String UNMODIFIABLE = "this graph is unmodifiable"; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructor for GraphSquare. - * - * @param g the graph of which a square is to be created. - * @param createLoops - */ - public GraphSquare(final Graph g, final boolean createLoops) - { - super(g.getEdgeFactory(), false, createLoops); - Graphs.addAllVertices(this, g.vertexSet()); - addSquareEdges(g, createLoops); - - if (g instanceof ListenableGraph) { - ((ListenableGraph) g).addGraphListener( - new GraphListener() { - public void edgeAdded(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - addEdgesStartingAt( - g, - g.getEdgeSource(edge), - g.getEdgeTarget(edge), - createLoops); - addEdgesStartingAt( - g, - g.getEdgeTarget(edge), - g.getEdgeSource(edge), - createLoops); - } - - public void edgeRemoved(GraphEdgeChangeEvent e) - { // this is not a very performant implementation - GraphSquare.super.removeAllEdges(edgeSet()); - addSquareEdges(g, createLoops); - } - - public void vertexAdded(GraphVertexChangeEvent e) - { - } - - public void vertexRemoved(GraphVertexChangeEvent e) - { - } - }); - } - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see Graph#addEdge(Object, Object) - */ - public E addEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#addEdge(Object, Object, E) - */ - public boolean addEdge(V sourceVertex, V targetVertex, E e) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#addVertex(Object) - */ - public boolean addVertex(V v) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllEdges(Collection) - */ - public boolean removeAllEdges(Collection edges) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllEdges(V, V) - */ - public Set removeAllEdges(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllVertices(Collection) - */ - public boolean removeAllVertices(Collection vertices) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeEdge(E) - */ - public boolean removeEdge(E e) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeEdge(V, V) - */ - public E removeEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeVertex(V) - */ - public boolean removeVertex(V v) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - private void addEdgesStartingAt( - final Graph g, - final V v, - final V u, - boolean createLoops) - { - if (!g.containsEdge(v, u)) { - return; - } - - final List adjVertices = Graphs.neighborListOf(g, u); - - for (int i = 0; i < adjVertices.size(); i++) { - final V w = adjVertices.get(i); - - if (g.containsEdge(u, w) && ((v != w) || createLoops)) { - super.addEdge(v, w); - } - } - } - - private void addSquareEdges(Graph g, boolean createLoops) - { - for (V v : g.vertexSet()) { - List adjVertices = Graphs.neighborListOf(g, v); - - for (int i = 0; i < adjVertices.size(); i++) { - addEdgesStartingAt(g, v, adjVertices.get(i), createLoops); - } - } - } -} - -// End GraphSquare.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphTests.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphTests.java deleted file mode 100644 index 8c099e1fa87..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/GraphTests.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.jgrapht.experimental; - -import java.util.*; - -import org.jgrapht.*; - - -public final class GraphTests -{ - //~ Constructors ----------------------------------------------------------- - - private GraphTests() - { - } - - //~ Methods ---------------------------------------------------------------- - - public static boolean isEmpty(Graph g) - { - return g.edgeSet().isEmpty(); - } - - public static boolean isComplete(Graph g) - { - int n = g.vertexSet().size(); - return g.edgeSet().size() - == (n * (n - 1) / 2); - } - - public static boolean isConnected(Graph g) - { - int numVertices = g.vertexSet().size(); - int numEdges = g.edgeSet().size(); - - if (numEdges < (numVertices - 1)) { - return false; - } - if ((numVertices < 2) - || (numEdges > ((numVertices - 1) * (numVertices - 2) / 2))) - { - return true; - } - - Set known = new HashSet(); - LinkedList queue = new LinkedList(); - V v = g.vertexSet().iterator().next(); - - queue.add(v); // start with node 1 - known.add(v); - - while (!queue.isEmpty()) { - v = queue.removeFirst(); - for ( - Iterator it = Graphs.neighborListOf(g, v).iterator(); - it.hasNext();) - { - v = it.next(); - if (!known.contains(v)) { - known.add(v); - queue.add(v); - } - } - } - return known.size() == numVertices; - } - - public static boolean isTree(Graph g) - { - return isConnected(g) - && (g.edgeSet().size() == (g.vertexSet().size() - 1)); - } - - public static boolean isBipartite(Graph g) - { - if ((4 * g.edgeSet().size()) - > (g.vertexSet().size() * g.vertexSet().size())) - { - return false; - } - if (isEmpty(g)) { - return true; - } - - Set unknown = new HashSet(g.vertexSet()); - LinkedList queue = new LinkedList(); - V v = unknown.iterator().next(); - Set odd = new HashSet(); - - queue.add(v); - - while (!unknown.isEmpty()) { - if (queue.isEmpty()) { - queue.add(unknown.iterator().next()); - } - - v = queue.removeFirst(); - unknown.remove(v); - - for ( - Iterator it = Graphs.neighborListOf(g, v).iterator(); - it.hasNext();) - { - V n = it.next(); - if (unknown.contains(n)) { - queue.add(n); - if (!odd.contains(v)) { - odd.add(n); - } - } else if (!(odd.contains(v) ^ odd.contains(n))) { - return false; - } - } - } - return true; - } -} - -// End GraphTests.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/PartiteRandomGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/PartiteRandomGraphGenerator.java deleted file mode 100644 index 0f9981bc710..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/PartiteRandomGraphGenerator.java +++ /dev/null @@ -1,171 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * PartiteRandomGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 13-Sep-2004 : Initial revision (MB); - * - */ -// package org.jgrapht.generate; -package org.jgrapht.experimental; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; - - -/** - * PartiteRandomGraphGenerator generates a partite uniform random - * graph of any size. A partite uniform random graph contains edges chosen - * independently uniformly at random from the set of possible edges between - * partition classes. - * - * @author Michael Behrisch - * @since Sep 13, 2004 - */ -public class PartiteRandomGraphGenerator - implements GraphGenerator -{ - //~ Instance fields -------------------------------------------------------- - - private final int [] numVertices; - private final int numEdges; - - //~ Constructors ----------------------------------------------------------- - - /** - * Construct a new PartiteRandomGraphGenerator for a bipartite graph. - * - * @param numVertices1 number of vertices in the first partition - * @param numVertices2 number of vertices in the second partition - * @param numEdges number of edges to be generated - * - * @throws IllegalArgumentException - */ - public PartiteRandomGraphGenerator( - int numVertices1, - int numVertices2, - int numEdges) - { - if ((numVertices1 < 0) || (numVertices2 < 0)) { - throw new IllegalArgumentException("must be non-negative"); - } - - if ((numEdges < 0) || (numEdges > (numVertices1 * numVertices2))) { - throw new IllegalArgumentException("illegal number of edges"); - } - - final int [] numVertices = { - numVertices1, - numVertices2 - }; - this.numVertices = numVertices; - this.numEdges = numEdges; - } - - /** - * Construct a new PartiteRandomGraphGenerator for a k-partite graph. - * - * @param numVertices number of vertices in the k partitions - * @param numEdges number of edges to be generated between any two - * partitions - * - * @throws IllegalArgumentException - */ - public PartiteRandomGraphGenerator(int [] numVertices, int numEdges) - { - if (numEdges < 0) { - throw new IllegalArgumentException("illegal number of edges"); - } - - for (int i = 0; i < numVertices.length; i++) { - if (numVertices[i] < 0) { - throw new IllegalArgumentException("must be non-negative"); - } - - for (int j = 0; j < i; j++) { - if (numEdges > (numVertices[i] * numVertices[j])) { - throw new IllegalArgumentException( - "illegal number of edges"); - } - } - } - - this.numVertices = numVertices; - this.numEdges = numEdges; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * TODO hb 30-nov-05: document me - * - * @param target - * @param vertexFactory - * @param resultMap some array of vertices - * - * @see GraphGenerator#generateGraph - */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) - { - Object [][] vertices = new Object[numVertices.length][]; - - for (int i = 0; i < numVertices.length; i++) { - vertices[i] = - RandomGraphHelper.addVertices( - target, - vertexFactory, - numVertices[i]); - - if (resultMap != null) { - resultMap.put(Integer.toString(i), vertices[i]); - } - - for (int j = 0; j < i; j++) { - RandomGraphHelper.addEdges( - target, - Arrays.asList(vertices[i]), - Arrays.asList(vertices[j]), - numEdges); - } - } - } -} - -// End PartiteRandomGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/RandomGraphHelper.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/RandomGraphHelper.java deleted file mode 100644 index 731f9e57c67..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/RandomGraphHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * RandomGraphHelper.java - * ------------------- - * (C) Copyright 2003-2008, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 13-Sep-2004 : Initial revision (MB); - * - */ -// package org.jgrapht.generate; -package org.jgrapht.experimental; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * UniformRandomGraphGenerator generates a uniform random graph - * of any size. A uniform random graph contains edges chosen independently - * uniformly at random from the set of all possible edges. - * - * @author Michael Behrisch - * @since Sep 13, 2004 - */ -public final class RandomGraphHelper -{ - //~ Static fields/initializers --------------------------------------------- - - private static final Random randSingleton = new Random(); - - //~ Constructors ----------------------------------------------------------- - - /** - * . - */ - private RandomGraphHelper() - { - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see GraphGenerator#generateGraph - */ - @SuppressWarnings("unchecked") - public static void addEdges( - Graph target, - List sourceVertices, - List destVertices, - int numEdges) - { - int sourceSize = sourceVertices.size(); - int destSize = destVertices.size(); - - for (int i = 0; i < numEdges; ++i) { - while ( - target.addEdge( - sourceVertices.get(randSingleton.nextInt( - sourceSize)), - destVertices.get(randSingleton.nextInt(destSize))) - == null) - { - ; - } - } - } - - /** - * . - * - * @param target - * @param vertexFactory - * @param numVertices - * - * @return - */ - @SuppressWarnings("unchecked") - public static Object [] addVertices( - Graph target, - VertexFactory vertexFactory, - int numVertices) - { - Object [] vertices = new Object[numVertices]; - - for (int i = 0; i < numVertices; ++i) { - vertices[i] = vertexFactory.createVertex(); - target.addVertex(vertices[i]); - } - - return vertices; - } -} - -// End RandomGraphHelper.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/UniformRandomGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/UniformRandomGraphGenerator.java deleted file mode 100644 index 6b34839cf11..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/UniformRandomGraphGenerator.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * UniformRandomGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 13-Sep-2004 : Initial revision (MB); - * - */ -// package org.jgrapht.generate; -package org.jgrapht.experimental; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; - - -/** - * UniformRandomGraphGenerator generates a uniform random graph - * of any size. A uniform random graph contains edges chosen independently - * uniformly at random from the set of all possible edges. - * - * @author Michael Behrisch - * @since Sep 13, 2004 - */ -public class UniformRandomGraphGenerator - implements GraphGenerator -{ - //~ Instance fields -------------------------------------------------------- - - private final int numEdges; - private final int numVertices; - - //~ Constructors ----------------------------------------------------------- - - /** - * Construct a new UniformRandomGraphGenerator. - * - * @param numVertices number of vertices to be generated - * @param numEdges number of edges to be generated - * - * @throws IllegalArgumentException - */ - public UniformRandomGraphGenerator(int numVertices, int numEdges) - { - if (numVertices < 0) { - throw new IllegalArgumentException("must be non-negative"); - } - - if ((numEdges < 0) - || (numEdges > (numVertices * (numVertices - 1) / 2))) - { - throw new IllegalArgumentException("illegal number of edges"); - } - - this.numVertices = numVertices; - this.numEdges = numEdges; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see GraphGenerator#generateGraph - */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) - { - Object [] vertices = - RandomGraphHelper.addVertices( - target, - vertexFactory, - numVertices); - RandomGraphHelper.addEdges( - target, - Arrays.asList(vertices), - Arrays.asList(vertices), - numEdges); - } -} - -// End UniformRandomGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ApproximationAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ApproximationAlgorithm.java deleted file mode 100644 index d3669968601..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ApproximationAlgorithm.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jgrapht.experimental.alg; - -import java.util.*; - - -public interface ApproximationAlgorithm -{ - //~ Methods ---------------------------------------------------------------- - - ResultType getUpperBound(Map optionalData); - - ResultType getLowerBound(Map optionalData); - - boolean isExact(); -} - -// End ApproximationAlgorithm.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ExactAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ExactAlgorithm.java deleted file mode 100644 index a3a4a2e1c89..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/ExactAlgorithm.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.jgrapht.experimental.alg; - -import java.util.*; - - -public interface ExactAlgorithm -{ - //~ Methods ---------------------------------------------------------------- - - ResultType getResult(Map optionalData); -} - -// End ExactAlgorithm.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/IntArrayGraphAlgorithm.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/IntArrayGraphAlgorithm.java deleted file mode 100644 index fdcbc5cbc46..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/IntArrayGraphAlgorithm.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * - */ -package org.jgrapht.experimental.alg; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * @author micha - */ -public abstract class IntArrayGraphAlgorithm -{ - //~ Instance fields -------------------------------------------------------- - - protected final List _vertices; - protected final int [][] _neighbors; - protected final Map _vertexToPos; - - //~ Constructors ----------------------------------------------------------- - - /** - * @param g - */ - public IntArrayGraphAlgorithm(final Graph g) - { - final int numVertices = g.vertexSet().size(); - _vertices = new ArrayList(numVertices); - _neighbors = new int[numVertices][]; - _vertexToPos = new HashMap(numVertices); - for (V vertex : g.vertexSet()) { - _neighbors[_vertices.size()] = new int[g.edgesOf(vertex).size()]; - _vertexToPos.put(vertex, _vertices.size()); - _vertices.add(vertex); - } - for (int i = 0; i < numVertices; i++) { - int nbIndex = 0; - final V vertex = _vertices.get(i); - for (E e : g.edgesOf(vertex)) { - _neighbors[i][nbIndex++] = - _vertexToPos.get(Graphs.getOppositeVertex(g, e, vertex)); - } - } - } -} - -// End IntArrayGraphAlgorithm.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/BrownBacktrackColoring.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/BrownBacktrackColoring.java deleted file mode 100644 index dfa66f9617d..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/BrownBacktrackColoring.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * - */ -package org.jgrapht.experimental.alg.color; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.alg.*; - - -/** - * @author micha - */ -public class BrownBacktrackColoring - extends IntArrayGraphAlgorithm - implements ExactAlgorithm -{ - //~ Instance fields -------------------------------------------------------- - - private int [] _color; - private int [] _colorCount; - private BitSet [] _allowedColors; - private int _chi; - - //~ Constructors ----------------------------------------------------------- - - /** - * @param g - */ - public BrownBacktrackColoring(final Graph g) - { - super(g); - } - - //~ Methods ---------------------------------------------------------------- - - void recursiveColor(int pos) - { - _colorCount[pos] = _colorCount[pos - 1]; - _allowedColors[pos].set(0, _colorCount[pos] + 1); - for (int i = 0; i < _neighbors[pos].length; i++) { - final int nb = _neighbors[pos][i]; - if (_color[nb] > 0) { - _allowedColors[pos].clear(_color[nb]); - } - } - for ( - int i = 1; - (i <= _colorCount[pos]) - && (_colorCount[pos] < _chi); - i++) - { - if (_allowedColors[pos].get(i)) { - _color[pos] = i; - if (pos < (_neighbors.length - 1)) { - recursiveColor(pos + 1); - } else { - _chi = _colorCount[pos]; - } - } - } - if ((_colorCount[pos] + 1) < _chi) { - _colorCount[pos]++; - _color[pos] = _colorCount[pos]; - if (pos < (_neighbors.length - 1)) { - recursiveColor(pos + 1); - } else { - _chi = _colorCount[pos]; - } - } - _color[pos] = 0; - } - - /* (non-Javadoc) - * @see org.jgrapht.experimental.alg.ExactAlgorithm#getResult() - */ - public Integer getResult(Map additionalData) - { - _chi = _neighbors.length; - _color = new int[_neighbors.length]; - _color[0] = 1; - _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); - if (additionalData != null) { - for (int i = 0; i < _vertices.size(); i++) { - additionalData.put(_vertices.get(i), _color[i]); - } - } - return _chi; - } -} - -// End BrownBacktrackColoring.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/GreedyColoring.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/GreedyColoring.java deleted file mode 100644 index d9a586bb58a..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/alg/color/GreedyColoring.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.jgrapht.experimental.alg.color; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.alg.*; - - -public class GreedyColoring - extends IntArrayGraphAlgorithm - implements ApproximationAlgorithm -{ - //~ Static fields/initializers --------------------------------------------- - - public static final int BEST_ORDER = 0; - public static final int NATURAL_ORDER = 1; - public static final int SMALLEST_DEGREE_LAST_ORDER = 2; - public static final int LARGEST_SATURATION_FIRST_ORDER = 3; - - //~ Instance fields -------------------------------------------------------- - - private int _order = BEST_ORDER; - - //~ Constructors ----------------------------------------------------------- - - /** - * @param g - */ - public GreedyColoring(final Graph g) - { - this(g, BEST_ORDER); - } - - /** - * @param g - */ - public GreedyColoring(final Graph g, final int method) - { - super(g); - _order = method; - } - - //~ Methods ---------------------------------------------------------------- - - int color(int [] order) - { - final int [] color = new int[_neighbors.length]; - int maxColor = 1; - BitSet usedColors = new BitSet(_neighbors.length); - - for (int i = 0; i < _neighbors.length; i++) { - final int v = (order == null) ? i : order[i]; - usedColors.clear(); - for (int j = 0; j < _neighbors[v].length; j++) { - final int nb = _neighbors[v][j]; - if (color[nb] > 0) { - usedColors.set(color[nb]); - } - } - color[v] = usedColors.nextClearBit(1); - if (color[v] > maxColor) { - maxColor = color[v]; - } - } - return maxColor; - } - - int [] smallestDegreeLastOrder() - { - final int [] order = new int[_neighbors.length]; - final int [] degree = new int[_neighbors.length]; - final List> buckets = - new ArrayList>(_neighbors.length); - int index = _neighbors.length - 1; - - for (int i = 0; i < _neighbors.length; i++) { - buckets.add(new ArrayList()); - degree[i] = _neighbors[i].length; - } - for (int i = 0; i < _neighbors.length; i++) { - buckets.get(degree[i]).add(i); - } - for (int i = 0; i < _neighbors.length; i++) { - while (buckets.get(i).size() > 0) { - final int s = buckets.get(i).size() - 1; - final int vertex = (Integer) buckets.get(i).get(s); - buckets.get(i).remove(s); - degree[vertex] = -1; - order[index--] = vertex; - for (int j = 0; j < _neighbors[vertex].length; j++) { - final int nb = _neighbors[vertex][j]; - if (degree[nb] >= 0) { - buckets.get(degree[nb]).remove(new Integer(nb)); - degree[nb]--; - buckets.get(degree[nb]).add(nb); - if (degree[nb] < i) { - i = degree[nb]; - } - } - } - } - } - return order; - } - - int [] largestSaturationFirstOrder() - { - final int [] satur = new int[_neighbors.length]; - final int [] buckets = new int[_neighbors.length]; - final int [] cumBucketSize = new int[_neighbors.length]; - final int [] bucketIndex = new int[_neighbors.length]; - int index = 0; - int maxSat = 0; - - for (int i = 0; i < _neighbors.length; i++) { - buckets[i] = i; - bucketIndex[i] = i; - } - cumBucketSize[0] = _neighbors.length; - while (index < _neighbors.length) { - while ( - (maxSat > 0) - && (cumBucketSize[maxSat] == cumBucketSize[maxSat - 1])) - { - cumBucketSize[maxSat--] = 0; - } - final int v = buckets[cumBucketSize[maxSat] - 1]; - cumBucketSize[maxSat]--; - satur[v] = -1; - index++; - for (int j = 0; j < _neighbors[v].length; j++) { - final int nb = (int) _neighbors[v][j]; - final int bi = bucketIndex[nb]; - if (satur[nb] >= 0) { - if (bi != (cumBucketSize[satur[nb]] - 1)) { - buckets[bi] = buckets[cumBucketSize[satur[nb]] - 1]; - buckets[cumBucketSize[satur[nb]] - 1] = nb; - bucketIndex[nb] = cumBucketSize[satur[nb]] - 1; - bucketIndex[buckets[bi]] = bi; - } - cumBucketSize[satur[nb]]--; - satur[nb]++; - if (cumBucketSize[satur[nb]] == 0) { - cumBucketSize[satur[nb]] = - cumBucketSize[satur[nb] - 1] + 1; - } - if (satur[nb] > maxSat) { - maxSat = satur[nb]; - } - } - } - } - Collections.reverse(Arrays.asList(buckets)); - return buckets; - } - - public Integer getLowerBound(Map optionalData) - { - return 0; - } - - public Integer getUpperBound(Map optionalData) - { - switch (_order) { - case BEST_ORDER: - return Math.min( - Math.min(color(null), color(smallestDegreeLastOrder())), - color(largestSaturationFirstOrder())); - case NATURAL_ORDER: - return color(null); - case SMALLEST_DEGREE_LAST_ORDER: - return color(smallestDegreeLastOrder()); - case LARGEST_SATURATION_FIRST_ORDER: - return color(largestSaturationFirstOrder()); - } - return _neighbors.length; - } - - public boolean isExact() - { - return false; - } -} - -// End GreedyColoring.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/dag/DirectedAcyclicGraph.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/dag/DirectedAcyclicGraph.java deleted file mode 100644 index 7a89dc663d2..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/dag/DirectedAcyclicGraph.java +++ /dev/null @@ -1,1199 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * DirectedAcyclicGraph.java - * ------------------- - * (C) Copyright 2008-2008, by Peter Giles and Contributors. - * - * Original Author: Peter Giles - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 17-Mar-2008 : Initial revision (PG); - * 23-Aug-2008 : Added VisitedBitSetImpl and made it the default (JVS); - * - */ -package org.jgrapht.experimental.dag; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - *

    DirectedAcyclicGraph implements a DAG that can be modified (vertices & - * edges added and removed), is guaranteed to remain acyclic, and provides fast - * topological order iteration.

    - * - *

    This is done using a dynamic topological sort which is based on the - * algorithm PK described in "D. Pearce & P. Kelly, 2007: A Dynamic - * Topological Sort Algorithm for Directed Acyclic Graphs", (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 HashMaps, which will have some effects on runtime, but - * also allows for vertex addition and removal, and other operations which are - * helpful for manipulating or combining DAGs. This storage mechanism is - * pluggable for subclassers.

    - * - *

    This class makes no claims to thread safety, and concurrent usage from - * multiple threads will produce undefined results.

    - * - * @author Peter Giles, gilesp@u.washington.edu - */ -public class DirectedAcyclicGraph - extends SimpleDirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 4522128427004938150L; - - //~ Instance fields -------------------------------------------------------- - - private TopoComparator topoComparator; - - private TopoOrderMapping topoOrderMap; - - private int maxTopoIndex = 0; - private int minTopoIndex = 0; - - // this update count is used to keep internal topological iterators honest - private long topologyUpdateCount = 0; - - /** - * Pluggable VisitedFactory implementation - */ - private VisitedFactory visitedFactory = new VisitedBitSetImpl(); - - /** - * Pluggable TopoOrderMappingFactory implementation - */ - private TopoOrderMappingFactory topoOrderFactory = new TopoVertexBiMap(); - - //~ Constructors ----------------------------------------------------------- - - public DirectedAcyclicGraph(Class arg0) - { - super(arg0); - initialize(); - } - - DirectedAcyclicGraph( - Class arg0, - VisitedFactory visitedFactory, - TopoOrderMappingFactory topoOrderFactory) - { - super(arg0); - if (visitedFactory != null) { - this.visitedFactory = visitedFactory; - } - if (topoOrderFactory != null) { - this.topoOrderFactory = topoOrderFactory; - } - initialize(); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * set the topoOrderMap based on the current factory, and create the - * comparator; - */ - private void initialize() - { - topoOrderMap = topoOrderFactory.getTopoOrderMapping(); - topoComparator = new TopoComparator(topoOrderMap); - } - - /** - * iterator will traverse the vertices in topological order, meaning that - * for a directed graph G = (V,E), if there exists a path from vertex va to - * vertex vb then va is guaranteed to come before vertex vb in the iteration - * order. - * - * @return an iterator that will traverse the graph in topological order - */ - public Iterator iterator() - { - return new TopoIterator(); - } - - /** - * adds the vertex if it wasn't already in the graph, and puts it at the top - * of the internal topological vertex ordering - */ - @Override public boolean addVertex(V v) - { - boolean added = super.addVertex(v); - - if (added) { - // add to the top - ++maxTopoIndex; - topoOrderMap.putVertex(maxTopoIndex, v); - - ++topologyUpdateCount; - } - - return added; - } - - /** - * adds the vertex if it wasn't already in the graph, and puts it either at - * the top or the bottom of the topological ordering, depending on the value - * of addToTop. This may provide useful optimizations for merging - * DirectedAcyclicGraphS that become connected. - * - * @param v - * @param addToTop - * - * @return - */ - public boolean addVertex(V v, boolean addToTop) - { - boolean added = super.addVertex(v); - - if (added) { - int insertIndex; - - // add to the top - if (addToTop) { - insertIndex = ++maxTopoIndex; - } else { - insertIndex = --minTopoIndex; - } - topoOrderMap.putVertex(insertIndex, v); - - ++topologyUpdateCount; - } - return added; - } - - /** - *

    Adds the given edge and updates the internal topological order for - * consistency IFF - * - *

      - *
    • there is not already an edge (fromVertex, toVertex) in the graph - *
    • the edge does not induce a cycle in the graph - *
    - *

    - * - * @return null if the edge is already in the graph, else the created edge - * is returned - * - * @throws IllegalArgumentException If either fromVertex or toVertex is not - * a member of the graph - * @throws CycleFoundException if the edge would induce a cycle in the graph - * - * @see Graph#addEdge(Object, Object, Object) - */ - public E addDagEdge(V fromVertex, V toVertex) - throws CycleFoundException - { - Integer lb = topoOrderMap.getTopologicalIndex(toVertex); - Integer ub = topoOrderMap.getTopologicalIndex(fromVertex); - - if ((lb == null) || (ub == null)) { - throw new IllegalArgumentException( - "vertices must be in the graph already!"); - } - - if (lb < ub) { - Set df = new HashSet(); - Set db = new HashSet(); - - // Discovery - Region affectedRegion = new Region(lb, ub); - Visited visited = visitedFactory.getInstance(affectedRegion); - - // throws CycleFoundException if there is a cycle - dfsF(toVertex, df, visited, affectedRegion); - - dfsB(fromVertex, db, visited, affectedRegion); - reorder(df, db, visited); - ++topologyUpdateCount; // if we do a reorder, than the topology has - // been updated - } - - return super.addEdge(fromVertex, toVertex); - } - - /** - * identical to {@link #addDagEdge(Object, Object)}, except an unchecked - * {@link IllegalArgumentException} is thrown if a cycle would have been - * induced by this edge - */ - @Override public E addEdge(V sourceVertex, V targetVertex) - { - E result = null; - try { - result = addDagEdge(sourceVertex, targetVertex); - } catch (CycleFoundException e) { - throw new IllegalArgumentException(e); - } - return result; - } - - /** - *

    Adds the given edge and updates the internal topological order for - * consistency IFF - * - *

      - *
    • the given edge is not already a member of the graph - *
    • there is not already an edge (fromVertex, toVertex) in the graph - *
    • the edge does not induce a cycle in the graph - *
    - *

    - * - * @return true if the edge was added to the graph - * - * @throws CycleFoundException if adding an edge (fromVertex, toVertex) to - * the graph would induce a cycle. - * - * @see Graph#addEdge(Object, Object, Object) - */ - public boolean addDagEdge(V fromVertex, V toVertex, E e) - throws CycleFoundException - { - if (e == null) { - throw new NullPointerException(); - } else if (containsEdge(e)) { - return false; - } - - Integer lb = topoOrderMap.getTopologicalIndex(toVertex); - Integer ub = topoOrderMap.getTopologicalIndex(fromVertex); - - if ((lb == null) || (ub == null)) { - throw new IllegalArgumentException( - "vertices must be in the graph already!"); - } - - if (lb < ub) { - Set df = new HashSet(); - Set db = new HashSet(); - - // Discovery - Region affectedRegion = new Region(lb, ub); - Visited visited = visitedFactory.getInstance(affectedRegion); - - // throws CycleFoundException if there is a cycle - dfsF(toVertex, df, visited, affectedRegion); - - dfsB(fromVertex, db, visited, affectedRegion); - reorder(df, db, visited); - ++topologyUpdateCount; // if we do a reorder, than the topology has - // been updated - } - - return super.addEdge(fromVertex, toVertex, e); - } - - /** - * identical to {@link #addDagEdge(Object, Object, Object)}, except an - * unchecked {@link IllegalArgumentException} is thrown if a cycle would - * have been induced by this edge - */ - @Override public boolean addEdge(V sourceVertex, V targetVertex, E edge) - { - boolean result; - try { - result = addDagEdge(sourceVertex, targetVertex, edge); - } catch (CycleFoundException e) { - throw new IllegalArgumentException(e); - } - return result; - } - - // note that this can leave holes in the topological ordering, which - // (depending on the TopoOrderMap implementation) can degrade performance - // for certain operations over time - @Override public boolean removeVertex(V v) - { - boolean removed = super.removeVertex(v); - - if (removed) { - Integer topoIndex = topoOrderMap.removeVertex(v); - - // contract minTopoIndex as we are able - if (topoIndex == minTopoIndex) { - while ( - (minTopoIndex < 0) - && (null == topoOrderMap.getVertex(minTopoIndex))) - { - ++minTopoIndex; - } - } - - // contract maxTopoIndex as we are able - if (topoIndex == maxTopoIndex) { - while ( - (maxTopoIndex > 0) - && (null == topoOrderMap.getVertex(maxTopoIndex))) - { - --maxTopoIndex; - } - } - - ++topologyUpdateCount; - } - - return removed; - } - - @Override public boolean removeAllVertices(Collection arg0) - { - boolean removed = super.removeAllVertices(arg0); - - topoOrderMap.removeAllVertices(); - - maxTopoIndex = 0; - minTopoIndex = 0; - - ++topologyUpdateCount; - - return removed; - } - - /** - * Depth first search forward, building up the set (df) of forward-connected - * vertices in the Affected Region - * - * @param vertex 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 - * @param topoIndexMap for quick lookups, a map from vertex to topo index in - * the AR - * @param ub the topo index of the original fromVertex -- used for cycle - * detection - * - * @throws CycleFoundException if a cycle is discovered - */ - private void dfsF( - V vertex, - Set df, - Visited visited, - Region affectedRegion) - throws CycleFoundException - { - int topoIndex = topoOrderMap.getTopologicalIndex(vertex); - - // 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.intValue() == affectedRegion.finish) { - // reset visited - try { - for (V visitedVertex : df) { - visited.clearVisited( - topoOrderMap.getTopologicalIndex(visitedVertex)); - } - } catch (UnsupportedOperationException e) { - // okay, fine, some implementations (ones that automatically - // clear 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)) - { - dfsF(nextVertex, df, visited, affectedRegion); // recurse - } - } - } - - /** - * Depth first search backward, building up the set (db) of back-connected - * vertices in the Affected Region - * - * @param vertex the vertex being visited - * @param db the set we are populating with back-connected vertices in the - * AR - * @param visited - * @param topoIndexMap - */ - private void dfsB( - V vertex, - Set db, - Visited visited, - Region affectedRegion) - { - // Assumption: vertex is in the AR and so we will get a topoIndex from - // the map - int topoIndex = topoOrderMap.getTopologicalIndex(vertex); - 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 prevousVertexTopoIndex != null, the vertex is in the - // Affected Region according to our topoIndexMap - - dfsB(previousVertex, db, visited, affectedRegion); - } - } - } - - @SuppressWarnings("unchecked") - private void reorder(Set df, Set db, Visited visited) - { - List topoDf = new ArrayList(df); - List topoDb = new ArrayList(db); - - Collections.sort(topoDf, topoComparator); - Collections.sort(topoDb, topoComparator); - - // merge these suckers together in topo 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); - } - } - - //~ Inner Interfaces ------------------------------------------------------- - - /** - * For performance tuning, an interface for storing the topological ordering - * - * @author gilesp - */ - public interface TopoOrderMapping - extends Serializable - { - /** - * add a vertex at the given topological index. - * - * @param index - * @param vertex - */ - public void putVertex(Integer index, V vertex); - - /** - * get the vertex at the given topological index. - * - * @param index - * - * @return - */ - public V getVertex(Integer index); - - /** - * get the topological index of the given vertex. - * - * @param vertex - * - * @return the index that the vertex is at, or null if the vertex isn't - * in the topological ordering - */ - public Integer getTopologicalIndex(V vertex); - - /** - * remove the given vertex from the topological ordering - * - * @param vertex - * - * @return the index that the vertex was at, or null if the vertex - * wasn't in the topological ordering - */ - public Integer removeVertex(V vertex); - - /** - * remove all vertices from the topological ordering - */ - public void removeAllVertices(); - } - - public interface TopoOrderMappingFactory - { - public TopoOrderMapping getTopoOrderMapping(); - } - - /** - * this interface allows specification of a strategy for marking vertices as - * visited (based on their topological index, so the vertex type isn't part - * of the interface). - */ - public interface Visited - { - /** - * mark the given topological index as visited - * - * @param index the topological index - */ - public void setVisited(int index); - - /** - * has the given topological index been visited? - * - * @param index the topological index - */ - public boolean getVisited(int index); - - /** - * Clear the visited state of the given topological index - * - * @param index - * - * @throws UnsupportedOperationException if the implementation doesn't - * support (or doesn't need) clearance. For example, if the factory - * vends a new instance every time, it is a waste of cycles to clear the - * state after the search of the Affected Region is done, so an - * UnsupportedOperationException *should* be thrown. - */ - public void clearVisited(int index) - throws UnsupportedOperationException; - } - - /** - * interface for a factory that vends Visited implementations - * - * @author gilesp - */ - public interface VisitedFactory - extends Serializable - { - public Visited getInstance(Region affectedRegion); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * Note, this is a lazy and incomplete implementation, with assumptions that - * inputs are in the given topoIndexMap - * - * @param - * - * @author gilesp - */ - private static class TopoComparator - implements Comparator, - Serializable - { - /** - */ - private static final long serialVersionUID = 1L; - - private TopoOrderMapping topoOrderMap; - - public TopoComparator(TopoOrderMapping topoOrderMap) - { - this.topoOrderMap = topoOrderMap; - } - - public int compare(V o1, V o2) - { - return topoOrderMap.getTopologicalIndex(o1).compareTo( - topoOrderMap.getTopologicalIndex(o2)); - } - } - - /** - * a dual HashMap implementation - * - * @author gilesp - */ - private class TopoVertexBiMap - implements TopoOrderMapping, - TopoOrderMappingFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final Map topoToVertex = new HashMap(); - private final Map vertexToTopo = new HashMap(); - - public void putVertex(Integer index, V vertex) - { - topoToVertex.put(index, vertex); - vertexToTopo.put(vertex, index); - } - - public V getVertex(Integer index) - { - return topoToVertex.get(index); - } - - public Integer getTopologicalIndex(V vertex) - { - Integer topoIndex = vertexToTopo.get(vertex); - return topoIndex; - } - - public Integer removeVertex(V vertex) - { - Integer topoIndex = vertexToTopo.remove(vertex); - if (topoIndex != null) { - topoToVertex.remove(topoIndex); - } - return topoIndex; - } - - public void removeAllVertices() - { - vertexToTopo.clear(); - topoToVertex.clear(); - } - - public TopoOrderMapping getTopoOrderMapping() - { - return this; - } - } - - /** - * For performance and flexibility uses an ArrayList for topological index - * to vertex mapping, and a HashMap for vertex to topological index mapping. - * - * @author gilesp - */ - public class TopoVertexMap - implements TopoOrderMapping, - TopoOrderMappingFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final List topoToVertex = new ArrayList(); - private final Map vertexToTopo = new HashMap(); - - 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); - } - - public V getVertex(Integer index) - { - return topoToVertex.get(translateIndex(index)); - } - - public Integer getTopologicalIndex(V vertex) - { - return vertexToTopo.get(vertex); - } - - public Integer removeVertex(V vertex) - { - Integer topoIndex = vertexToTopo.remove(vertex); - if (topoIndex != null) { - topoToVertex.set(translateIndex(topoIndex), null); - } - return topoIndex; - } - - public void removeAllVertices() - { - vertexToTopo.clear(); - topoToVertex.clear(); - } - - public TopoOrderMapping getTopoOrderMapping() - { - return this; - } - - /** - * 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|. - * - * @param unscaledIndex - * - * @return the ArrayList index - */ - private final int translateIndex(int index) - { - if (index >= 0) { - return 2 * index; - } - return -1 * ((index * 2) - 1); - } - } - - /** - * Region is an *inclusive* range of indices. Esthetically displeasing, but - * convenient for our purposes. - * - * @author gilesp - */ - public static class Region - implements Serializable - { - /** - */ - private static final long serialVersionUID = 1L; - - public final int start; - public final int finish; - - public Region(int start, int finish) - { - if (start > finish) { - throw new IllegalArgumentException( - "(start > finish): invariant broken"); - } - this.start = start; - this.finish = finish; - } - - public int getSize() - { - return (finish - start) + 1; - } - - public boolean isIn(int index) - { - return (index >= start) && (index <= finish); - } - } - - /** - * This implementation is close to the performance of VisitedArrayListImpl, - * with 1/8 the memory usage. - * - * @author perfecthash - */ - public static class VisitedBitSetImpl - implements Visited, - VisitedFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final BitSet visited = new BitSet(); - - private Region affectedRegion; - - public Visited getInstance(Region affectedRegion) - { - this.affectedRegion = affectedRegion; - - return this; - } - - public void setVisited(int index) - { - visited.set(translateIndex(index), true); - } - - public boolean getVisited(int index) - { - return visited.get(translateIndex(index)); - } - - 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|. - * - * @param unscaledIndex - * - * @return the ArrayList index - */ - private int translateIndex(int index) - { - return index - affectedRegion.start; - } - } - - /** - * 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 gilesp - */ - public static class VisitedArrayListImpl - implements Visited, - VisitedFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final List visited = new ArrayList(); - - private Region affectedRegion; - - public Visited getInstance(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; - } - - public void setVisited(int index) - { - visited.set(translateIndex(index), Boolean.TRUE); - } - - public boolean getVisited(int index) - { - Boolean result = null; - - result = visited.get(translateIndex(index)); - - return result; - } - - 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|. - * - * @param unscaledIndex - * - * @return the ArrayList index - */ - private int translateIndex(int index) - { - return index - affectedRegion.start; - } - } - - /** - * 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 gilesp - */ - public static class VisitedHashSetImpl - implements Visited, - VisitedFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final Set visited = new HashSet(); - - public Visited getInstance(Region affectedRegion) - { - visited.clear(); - return this; - } - - public void setVisited(int index) - { - visited.add(index); - } - - public boolean getVisited(int index) - { - return visited.contains(index); - } - - public void clearVisited(int index) - throws UnsupportedOperationException - { - throw new UnsupportedOperationException(); - } - } - - /** - * 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 gilesp - */ - public static class VisitedArrayImpl - implements Visited, - VisitedFactory - { - /** - */ - private static final long serialVersionUID = 1L; - - private final boolean [] visited; - - private final Region region; - - /** - * Constructs empty factory instance - */ - public VisitedArrayImpl() - { - this(null); - } - - 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()]; - } - } - - public Visited getInstance(Region affectedRegion) - { - return new VisitedArrayImpl(affectedRegion); - } - - public void setVisited(int index) - { - try { - visited[index - region.start] = true; - } catch (ArrayIndexOutOfBoundsException e) { - /* - log.error("Visited set operation out of region boundaries", e); - */ - throw e; - } - } - - public boolean getVisited(int index) - { - try { - return visited[index - region.start]; - } catch (ArrayIndexOutOfBoundsException e) { - /* - log.error("Visited set operation out of region boundaries", e); - */ - throw e; - } - } - - public void clearVisited(int index) - throws UnsupportedOperationException - { - throw new UnsupportedOperationException(); - } - } - - /** - * Exception used in dfsF when a cycle is found - * - * @author gilesp - */ - public static class CycleFoundException - extends Exception - { - private static final long serialVersionUID = 5583471522212552754L; - } - - /** - * iterator which follows topological order - * - * @author gilesp - */ - private class TopoIterator - implements Iterator - { - private int currentTopoIndex; - private final long updateCountAtCreation; - private Integer nextIndex = null; - - public TopoIterator() - { - updateCountAtCreation = topologyUpdateCount; - currentTopoIndex = minTopoIndex - 1; - } - - public boolean hasNext() - { - if (updateCountAtCreation != topologyUpdateCount) { - throw new ConcurrentModificationException(); - } - - nextIndex = getNextIndex(); - return nextIndex != null; - } - - public V next() - { - if (updateCountAtCreation != topologyUpdateCount) { - throw new ConcurrentModificationException(); - } - - if (nextIndex == null) { - // find nextIndex - nextIndex = getNextIndex(); - } - if (nextIndex == null) { - throw new NoSuchElementException(); - } - currentTopoIndex = nextIndex; - nextIndex = null; - return topoOrderMap.getVertex(currentTopoIndex); //topoToVertex.get(currentTopoIndex); - } - - public void remove() - { - if (updateCountAtCreation != topologyUpdateCount) { - throw new ConcurrentModificationException(); - } - - V vertexToRemove = null; - if (null - != (vertexToRemove = - topoOrderMap.getVertex( - currentTopoIndex))) - { - 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 (null != topoOrderMap.getVertex(i)) { - return i; - } - } - return null; - } - } -} - -// End DirectedAcyclicGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparator.java deleted file mode 100644 index 433d1a1a2f6..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparator.java +++ /dev/null @@ -1,93 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -/** - * This interface distinguishes between Equivalence sets. - * - *

    It is similar, in concept, to the Object.hashcode() and Object.equals() - * methods, but instead of checking whether two objects are equal, it is used to - * check whether they are part of the same Equivalence group, where the - * definition of an "equivalence" is defined by the implementation of this - * interface. - * - *

    A specific usage of it is shown below, but it may be used outside of the - * graph-theory class library. - * - *

    In Isomorphism, edges/vertexes matching may relay on none/some/all of the - * vertex/edge properties. For example, if a vertex representing a person - * contains two properties: gender(male/female) and person name(string), we can - * decide that to check isomorphism in vertex groups of gender only. Meaning if - * this is the graph: - * - *

    (male,"Don")---->(female,"Dana")--->(male,"John") - * - *

    if there is no equivalence set at all , this graph can be described as: - * (1)---->(2)---->(3) - * - *

    if the equivalence set is determined only by the gender property : - * (male)---->(female)---->(male) - * - *

    and if it is determined by both properties: (the original figure) The - * isomorphism inspection may return different result according to this choice. - * If the other graph is: (male,"Don")--->(male,"Sunny")---->(male,"Jo") In no - * eq.set they are Isomorphic, but for the two other cases they are not. Other - * examples: Nodes with the same degree, Edges with the same weight, Graphs with - * the same number of nodes and edges. - * - * @param the type of the elements in the set - * @param the type of the context the element is compared against, e.g. a - * Graph - * - * @author Assaf - * @since Jul 15, 2005 - */ -public interface EquivalenceComparator -{ - //~ Methods ---------------------------------------------------------------- - - public boolean equivalenceCompare( - E arg1, - E arg2, - C context1, - C context2); - - public int equivalenceHashcode(E arg1, C context); -} - -// End EquivalenceComparator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChain.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChain.java deleted file mode 100644 index 4f7871b2e38..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChain.java +++ /dev/null @@ -1,71 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceComparatorChain.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -/** - * A container of comparators, which are tested in a chain until the first - * result can be supplied. It implements the EquivalenceComparator, so chains - * can include other chains. The first check will use the current comparator and - * not the next one. So, make sure to use the one which has better performance - * first. (This class follows the "Composite" design-pattern). - * - * @param the type of the elements in the set - * @param the type of the context the element is compared against, e.g. a - * Graph - * - * @author Assaf - * @since Jul 22, 2005 - */ -public interface EquivalenceComparatorChain - extends EquivalenceComparator -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Adds a comparator which will also test equivalence. For - * equivalenceCompare(), the return value is a logical AND of the two - * comparators. The first check will use the first comparator before the - * next one. Make sure to put the one which has better performance first. - * For equivalenceHashcode(), the resulting hashes will be rehashed - * together. This method may be used multiple times to create a long "chain" - * of comparators. - */ - public void appendComparator(EquivalenceComparator comparatorAfter); -} - -// End EquivalenceComparatorChain.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChainBase.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChainBase.java deleted file mode 100644 index b6f9fd324ee..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceComparatorChainBase.java +++ /dev/null @@ -1,167 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceComparatorChainBase.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: EquivalenceComparatorChainBase.java 485 2006-06-26 09:12:14Z perfecthash - * $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -import java.util.*; - - -/** - * This class implements comparator chaining. - * - *

    Usage examples: - *

  • graph-theory, node equivalence: You can create a comparator for - * the inDegree of a node, another for the total weight of outDegree edges, and - * a third which checks the business content of the node. You know that the - * first topological comparators has dozens of different groups, but the - * buisness comparator has only two, and they are hard to check . The best - * performance will be gained by: - * - *
    - *

    EquivalenceComparatorChainBase eqChain = new - * EquivalenceComparatorChainBase(fastNodesDegreeComparator); - * - *

    eqChain.addComparatorAfter(ABitSlowerEdgeWeightComparator); - * - *

    eqChain.addComparatorAfter(slowestBuisnessContentsComparator); - *

    - * - * @param the type of the elements in the set - * @param the type of the context the element is compared against, e.g. a - * Graph - * - * @author Assaf - * @since Jul 22, 2005 - */ -public class EquivalenceComparatorChainBase - implements EquivalenceComparatorChain -{ - //~ Instance fields -------------------------------------------------------- - - private List> chain; - - //~ Constructors ----------------------------------------------------------- - - /** - */ - public EquivalenceComparatorChainBase( - EquivalenceComparator firstComaparator) - { - this.chain = - new LinkedList>(); - this.chain.add(firstComaparator); - } - - //~ Methods ---------------------------------------------------------------- - - /* (non-Javadoc) - * @see - * - * - * - * - * - * org.jgrapht.experimental.equivalence.EquivalenceComparatorChain#addComparatorAfter(org.jgrapht.experimental.equivalence.EquivalenceComparator) - */ - @SuppressWarnings("unchecked") - public void appendComparator(EquivalenceComparator comparatorAfter) - { - if (comparatorAfter != null) { - this.chain.add(comparatorAfter); - } - } - - /** - * Implements logical AND between the comparators results. Iterates through - * the comparators chain until one of them returns false. If none returns - * false, this method returns true. - * - * @see EquivalenceComparator#equivalenceCompare(Object, Object, Object, - * Object) - */ - public boolean equivalenceCompare( - E arg1, - E arg2, - C context1, - C context2) - { - for ( - EquivalenceComparator currentComparator - : this.chain) - { - if (!currentComparator.equivalenceCompare( - arg1, - arg2, - context1, - context2)) - { - return false; - } - } - return true; - } - - /** - * Rehashes the concatenation of the results of all single hashcodes. - * - * @see EquivalenceComparator#equivalenceHashcode(Object, Object) - */ - public int equivalenceHashcode(E arg1, C context) - { - StringBuffer hashStringBuffer = new StringBuffer(); - for ( - ListIterator> iter = - this.chain.listIterator(); - iter.hasNext();) - { - EquivalenceComparator currentComparator = - iter.next(); - int currentHashCode = - currentComparator.equivalenceHashcode(arg1, context); - hashStringBuffer.append(currentHashCode); - - // add a delimeter only if needed for next - if (iter.hasNext()) { - hashStringBuffer.append('+'); - } - } - return hashStringBuffer.toString().hashCode(); - } -} - -// End EquivalenceComparatorChainBase.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSet.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSet.java deleted file mode 100644 index 43b976a9d2f..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSet.java +++ /dev/null @@ -1,206 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceSet.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -import java.util.*; - - -/** - * EquivalenceSet is a Set of elements which have been determined to be - * equivalent using EquivalenceComparator. The class makes sure the set size - * will be one or more. - *
  • The group can only be created using the factory method - * createGroupWithElement(). - *
  • The equals and hashcode of a group uses the EquivalenceComparator on one - * of the group members, thus it is actually checking whether the "other" is in - * the same group. - * - * @param the type of the elements in the set - * @param the type of the context the element is compared against, e.g. a - * Graph - * - * @author Assaf - * @since Jul 21, 2005 - */ -public class EquivalenceSet -{ - //~ Instance fields -------------------------------------------------------- - - /** - * The comparator used to define the group - */ - protected EquivalenceComparator eqComparator; - protected C comparatorContext; - - /** - * Contains the current elements of the group - */ - protected Set elementsSet; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructs a new EquivalenceSet, filled with the aElement parameter and a - * reference to the comparator which is used. - */ - public EquivalenceSet( - E aElement, - EquivalenceComparator aEqComparator, - C aComparatorContext) - { - this.eqComparator = aEqComparator; - this.comparatorContext = aComparatorContext; - - this.elementsSet = new HashSet(); - this.elementsSet.add(aElement); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns an arbitrary object from the group. There is no guarantee as to - * which will be returned, and whether the same will be returned on the next - * call. - */ - public E getRepresentative() - { - return elementsSet.iterator().next(); - } - - public C getContext() - { - return this.comparatorContext; - } - - public int size() - { - return elementsSet.size(); - } - - /** - * Adds an element to the group. It does not check it for equivalance . You - * must make sure it does, using equals(). - */ - public void add(E element) - { - this.elementsSet.add(element); - } - - public boolean equivalentTo(E aOther, C aOtherContext) - { - boolean result = - this.eqComparator.equivalenceCompare( - this.getRepresentative(), - aOther, - this.comparatorContext, - aOtherContext); - return result; - } - - /** - * Uses the equivalenceCompare() of the comparator to compare a - * representation of this group, taken using this.getRepresentative(), and a - * representation of the other object, which may be the object itself, or, - * if it is an equivalence group too, other.getRepresentative() - */ - // FIXME REVIEW hb 26-Jan-2006: I think throwing the exception is kind of - // odd, - // - it feels like violating the contract of Object.equals() - // From what I understand, comparing any object to any other object should - // be - // possible at all times and simply return false if they are not equal. - // Uncomparable objects beeing unequal. - // Suggestion: remove the exception, at best, test on this specific class - // and - // write a warning or some such. - - @SuppressWarnings("unchecked") - public boolean equals(Object other) - { - E otherRepresentative = null; - C otherContext = null; - if (other instanceof EquivalenceSet) { - otherRepresentative = - ((EquivalenceSet) other).getRepresentative(); - otherContext = ((EquivalenceSet) other).getContext(); - } else { - throw new ClassCastException( - "can check equal() only of EqualityGroup"); - } - - boolean result = - this.eqComparator.equivalenceCompare( - this.getRepresentative(), - otherRepresentative, - this.comparatorContext, - otherContext); - return result; - } - - /** - * Uses a representative to calculate the group hashcode using - * equivalenceHashcode(). - * - * @see java.lang.Object#hashCode() - */ - public int hashCode() - { - int result = - this.eqComparator.equivalenceHashcode( - this.getRepresentative(), - this.comparatorContext); - return result; - } - - public String toString() - { - return "Eq.Group=" + this.elementsSet.toString(); - } - - /** - * Returns the elements of the group. The order of the elements in the - * returned array is not guaranteed. In other words, two calls to the same - * object may return different order. - */ - public Object [] toArray() - { - return this.elementsSet.toArray(); - } -} - -// End EquivalenceSet.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSetCreator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSetCreator.java deleted file mode 100644 index 0b3aeee2fb3..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/EquivalenceSetCreator.java +++ /dev/null @@ -1,262 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceSetCreator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -import java.util.*; - - -/** - * FIXME Document me. - * - * @param the type of the elements in the set - * @param the type of the context the element is compared against, e.g. a - * Graph TODO hb 060208: REVIEW: Using an array for aElementsArray causes - * problems with generics elsewhere - changed to List? - * - * @author Assaf - * @since Jul 21, 2005 - */ -public class EquivalenceSetCreator -{ - //~ Static fields/initializers --------------------------------------------- - - private static final EqGroupSizeComparator groupSizeComparator = - new EqGroupSizeComparator(); - - //~ Methods ---------------------------------------------------------------- - - /** - * Checks for equivalance groups in the aElementsArray. Returns an ordered - * array of them, where the smallest one is the first in the array. - * - * @param aElementsArray - * @param aEqComparator - * - * @deprecated To improve type-safety when using generics, use {@link - * #createEqualityGroupOrderedArray(Collection, EquivalenceComparator, - * Object)} - */ - @Deprecated public static EquivalenceSet [] - createEqualityGroupOrderedArray( - EE [] aElementsArray, - EquivalenceComparator aEqComparator, - CC aContext) - { - return (createEqualityGroupOrderedArray( - Arrays.asList(aElementsArray), - aEqComparator, - aContext)); - // ArrayList> arrayList = new - // ArrayList>(); - // - // HashMap>> map - // = createEqualityGroupMap(aElementsArray, aEqComparator, - // aContext); // each of the map values is a list with one or - // more groups in it. // Object[] array = map.values().toArray(); - // // for (int i = 0; i < array.length; i++) // { // List list = - // (List)array[i]; - // - // for (List> list : - // map.values() ) { for (EquivalenceSet - // eSet : list ) { arrayList.add( eSet ); } } - // - // - // now we got all the eq. groups in an array list. we need to sort - // // them EquivalenceSet [] resultArray = new EquivalenceSet - // [arrayList.size()]; arrayList.toArray(resultArray); - // Arrays.sort(resultArray, groupSizeComparator); return - // resultArray; - } - - /** - * Checks for equivalance groups in the aElementsArray. Returns an ordered - * array of them, where the smallest one is the first in the array. - * - * @param elements - * @param aEqComparator TODO hb 060208: Using an array for aElementsArray - * causes problems with generics elsewhere - change to List? - */ - public static EquivalenceSet [] createEqualityGroupOrderedArray( - Collection elements, - EquivalenceComparator aEqComparator, - CC aContext) - { - ArrayList> arrayList = - new ArrayList>(); - - HashMap>> map = - createEqualityGroupMap(elements, aEqComparator, aContext); - // each of the map values is a list with one or more groups in it. - // Object[] array = map.values().toArray(); - // for (int i = 0; i < array.length; i++) - // { - // List list = (List)array[i]; - - for (List> list : map.values()) { - for (EquivalenceSet eSet : list) { - arrayList.add(eSet); - } - } - - // now we got all the eq. groups in an array list. we need to sort - // them - EquivalenceSet [] resultArray = new EquivalenceSet[arrayList.size()]; - arrayList.toArray(resultArray); - Arrays.sort(resultArray, groupSizeComparator); - return resultArray; - } - - /** - * The data structure we use to store groups is a map, where the key is - * eqGroupHashCode, and the value is list, containing one or more eqGroup - * which match this hash. - * - * @param elements - * @param aEqComparator - * - * @return a hashmap with key=group hashcode , value = list of eq.groups - * which match that hash. TODO hb 060208: Using an array for aElementsArray - * causes problems with generics elsewhere - change to List? - */ - private static HashMap>> createEqualityGroupMap( - Collection elements, - EquivalenceComparator aEqComparator, - CC aComparatorContext) - { - HashMap>> equalityGroupMap = - new HashMap>>( - elements.size()); - - for (EE curentElement : elements) { - int hashcode = - aEqComparator.equivalenceHashcode( - curentElement, - aComparatorContext); - List> list = - equalityGroupMap.get(Integer.valueOf(hashcode)); - - // determine the type of value. It can be null(no value yet) , - // or a list of EquivalenceSet - - if (list == null) { - // create list with one element in it - list = new LinkedList>(); - list.add( - new EquivalenceSet( - curentElement, - aEqComparator, - aComparatorContext)); - - // This is the first one .add it to the map , in an eqGroup - equalityGroupMap.put(Integer.valueOf(hashcode), list); - } else { - boolean eqWasFound = false; - - // we need to check the groups in the list. If none are good, - // create a new one - for (EquivalenceSet eqGroup : list) { - if (eqGroup.equivalentTo( - curentElement, - aComparatorContext)) - { - // add it to the list and break - eqGroup.add(curentElement); - eqWasFound = true; - break; - } - } - - // if no match was found add it to the list as a new group - if (!eqWasFound) { - list.add( - new EquivalenceSet( - curentElement, - aEqComparator, - aComparatorContext)); - } - } - } - - return equalityGroupMap; - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * Functor used to order groups by size (number of elements in the group) - * from the smallest to the biggest. If they have the same size, uses the - * hashcode of the group to compare from the smallest to the biggest. Note - * that it is inconsistent with equals(). See Object.equals() javadoc. - * - * @author Assaf - * @since Jul 22, 2005 - */ - private static class EqGroupSizeComparator - implements Comparator - { - /** - * compare by size , then (if size equal) by hashcode - * - * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) - */ - @SuppressWarnings("unchecked") - public int compare(EquivalenceSet arg1, EquivalenceSet arg2) - { - int eqGroupSize1 = arg1.size(); - int eqGroupSize2 = arg2.size(); - if (eqGroupSize1 > eqGroupSize2) { - return 1; - } else if (eqGroupSize1 < eqGroupSize2) { - return -1; - } else { // size equal , compare hashcodes - int eqGroupHash1 = arg1.hashCode(); - int eqGroupHash2 = arg2.hashCode(); - if (eqGroupHash1 > eqGroupHash2) { - return 1; - } else if (eqGroupHash1 < eqGroupHash2) { - return -1; - } else { - return 0; - } - } - } - } -} - -// End EquivalenceSetCreator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/UniformEquivalenceComparator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/UniformEquivalenceComparator.java deleted file mode 100644 index 43c9afa96c1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/UniformEquivalenceComparator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * UniformEquivalenceComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -/** - * This Equivalence comparator acts as if all elements are in one big global - * equivalence class. Useful when a comparator is needed, but there is no - * important difference between the elements. equivalenceCompare() always return - * true; equivalenceHashcode() always returns 0. - * - * @author Assaf - * @since Jul 21, 2005 - */ -public class UniformEquivalenceComparator - implements EquivalenceComparator -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Always returns true. - * - * @see EquivalenceComparator#equivalenceCompare(Object, Object, Object, - * Object) - */ - public boolean equivalenceCompare( - E arg1, - E arg2, - C context1, - C context2) - { - return true; - } - - /** - * Always returns 0. - * - * @see EquivalenceComparator#equivalenceHashcode(Object, Object) - */ - public int equivalenceHashcode(E arg1, C context) - { - return 0; - } -} - -// End UniformEquivalenceComparator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/package.html b/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/package.html deleted file mode 100644 index dbc6049fdd6..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/equivalence/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Classes which enable working with Equivalence Sets. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AbstractExhaustiveIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AbstractExhaustiveIsomorphismInspector.java deleted file mode 100644 index d60683ce81f..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AbstractExhaustiveIsomorphismInspector.java +++ /dev/null @@ -1,423 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * AbstractExhaustiveIsomorphismInspector.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: AbstractExhaustiveIsomorphismInspector.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.experimental.permutation.*; -import org.jgrapht.util.*; - - -/** - * Abstract base for isomorphism inspectors which exhaustively test the possible - * mappings between graphs. The current algorithms do not support graphs with - * multiple edges (Multigraph / Pseudograph). For the maintainer: The reason is - * the use of GraphOrdering which currently does not support all graph types. - * - * @author Assaf Lehr - * @since May 20, 2005 ver5.3 - */ -abstract class AbstractExhaustiveIsomorphismInspector - implements GraphIsomorphismInspector -{ - //~ Static fields/initializers --------------------------------------------- - - public static EquivalenceComparator - edgeDefaultIsomorphismComparator = - new UniformEquivalenceComparator(); - public static EquivalenceComparator - vertexDefaultIsomorphismComparator = - new UniformEquivalenceComparator(); - - //~ Instance fields -------------------------------------------------------- - - protected EquivalenceComparator> - edgeComparator; - protected EquivalenceComparator> - vertexComparator; - - protected Graph graph1; - protected Graph graph2; - - private PrefetchIterator nextSupplier; - - // kept as member, to ease computations - private GraphOrdering lableGraph1; - private LinkedHashSet graph1VertexSet; - private LinkedHashSet graph2EdgeSet; - private CollectionPermutationIter vertexPermuteIter; - private Set currVertexPermutation; // filled every iteration, used in the - - //~ Constructors ----------------------------------------------------------- - - // result relation. - - /** - * @param graph1 - * @param graph2 - * @param vertexChecker eq. group checker for vertexes. If null, - * UniformEquivalenceComparator will be used as default (always return true) - * @param edgeChecker eq. group checker for edges. If null, - * UniformEquivalenceComparator will be used as default (always return true) - */ - public AbstractExhaustiveIsomorphismInspector( - Graph graph1, - Graph graph2, - - // XXX hb 060128: FOllowing parameter may need Graph - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - this.graph1 = graph1; - this.graph2 = graph2; - - if (vertexChecker != null) { - this.vertexComparator = vertexChecker; - } else { - this.vertexComparator = vertexDefaultIsomorphismComparator; - } - - // Unlike vertexes, edges have better performance, when not tested for - // Equivalence, so if the user did not supply one, use null - // instead of edgeDefaultIsomorphismComparator. - - if (edgeChecker != null) { - this.edgeComparator = edgeChecker; - } - - init(); - } - - /** - * Constructor which uses the default comparators. - * - * @param graph1 - * @param graph2 - * - * @see #AbstractExhaustiveIsomorphismInspector(Graph,Graph,EquivalenceComparator,EquivalenceComparator) - */ - public AbstractExhaustiveIsomorphismInspector( - Graph graph1, - Graph graph2) - { - this( - graph1, - graph2, - edgeDefaultIsomorphismComparator, - vertexDefaultIsomorphismComparator); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Inits needed data-structures , among them: - *
  • LabelsGraph which is a created in the image of graph1 - *
  • vertexPermuteIter which is created after the vertexes were divided to - * equivalence groups. This saves order-of-magnitude in performance, because - * the number of possible permutations dramatically decreases. - * - *

    for example: if the eq.group are even/odd - only two groups. A graph - * with consist of 10 nodes of which 5 are even , 5 are odd , will need to - * test 5!*5! (14,400) instead of 10! (3,628,800). - * - *

    besides the EquivalenceComparator`s supplied by the user, we also use - * predefined topological comparators. - */ - private void init() - { - this.nextSupplier = - new PrefetchIterator( - - // XXX hb 280106: I don't understand this warning, yet :-) - new NextFunctor()); - - this.graph1VertexSet = new LinkedHashSet(this.graph1.vertexSet()); - - // vertexPermuteIter will be null, if there is no match - this.vertexPermuteIter = - createPermutationIterator( - this.graph1VertexSet, - this.graph2.vertexSet()); - - this.lableGraph1 = - new GraphOrdering( - this.graph1, - this.graph1VertexSet, - this.graph1.edgeSet()); - - this.graph2EdgeSet = new LinkedHashSet(this.graph2.edgeSet()); - } - - /** - * Creates the permutation iterator for vertexSet2 . The subclasses may make - * either cause it to depend on equality groups or use vertexSet1 for it. - * - * @param vertexSet1 [i] may be reordered - * @param vertexSet2 [i] may not. - * - * @return permutation iterator - */ - protected abstract CollectionPermutationIter createPermutationIterator( - Set vertexSet1, - Set vertexSet2); - - /** - *

    1. Creates a LabelsGraph of graph1 which will serve as a source to all - * the comparisons which will follow. - * - *

    2. extract the edge array of graph2; it will be permanent too. - * - *

    3. for each permutation of the vertexes of graph2, test : - * - *

    3.1. vertices - * - *

    3.2. edges (in labelsgraph) - * - *

    Implementation Notes and considerations: Let's consider a trivial - * example: graph of strings "A","B","C" with two edges A->B,B->C. Let's - * assume for this example that the vertex comparator always returns true, - * meaning String value does not matter, only the graph structure does. So - * "D" "E" "A" with D->E->A will be isomorphic , but "A","B,"C"with - * A->B,A->C will not. - * - *

    First let's extract the important info for isomorphism from the graph. - * We don't care what the vertexes are, we care that there are 3 of them - * with edges from first to second and from second to third. So the source - * LabelsGraph will be: vertexes:[1,2,3] edges:[[1->2],[2->3]] Now we will - * do several permutations of D,E,A. A few examples: D->E , E->A - * [1,2,3]=[A,D,E] so edges are: 2->3 , 3->1 . does it match the source? NO. - * [1,2,3]=[D,A,E] so edges are: 1->3 , 3->2 . no match either. - * [1,2,3]=[D,E,A] so edges are: 1->2 , 2->3 . MATCH FOUND ! Trivial - * algorithm: We will iterate on all permutations - * [abc][acb][bac][bca][cab][cba]. (n! of them,3!=6) For each, first compare - * vertexes using the VertexComparator(always true). Then see that the edges - * are in the exact order 1st->2nd , 2nd->3rd. If we found a match stop and - * return true, otherwise return false; we will compare vetices and edges by - * their order (1st,2nd,3rd,etc) only. Two graphs are the same, by this - * order, if: 1. for each i, sourceVertexArray[i] is equivalent to - * targetVertexArray[i] 2. for each vertex, the edges which start in it (it - * is the source) goes to the same ordered vertex. For multiple ones, count - * them too. - * - * @return IsomorphismRelation for a permutation found, or null if no - * permutation was isomorphic - */ - private IsomorphismRelation findNextIsomorphicGraph() - { - boolean result = false; - IsomorphismRelation resultRelation = null; - if (this.vertexPermuteIter != null) { - // System.out.println("Souce LabelsGraph="+this.lableGraph1); - while (this.vertexPermuteIter.hasNext()) { - currVertexPermutation = this.vertexPermuteIter.getNextSet(); - - // compare vertexes - if (!areVertexSetsOfTheSameEqualityGroup( - this.graph1VertexSet, - currVertexPermutation)) - { - continue; // this one is not iso, so try the next one - } - - // compare edges - GraphOrdering currPermuteGraph = - new GraphOrdering( - this.graph2, - currVertexPermutation, - this.graph2EdgeSet); - - // System.out.println("target LablesGraph="+currPermuteGraph); - if (this.lableGraph1.equalsByEdgeOrder(currPermuteGraph)) { - // create result object. - resultRelation = - new IsomorphismRelation( - new ArrayList(graph1VertexSet), - new ArrayList(currVertexPermutation), - graph1, - graph2); - - // if the edge comparator exists, check equivalence by it - boolean edgeEq = - areAllEdgesEquivalent( - resultRelation, - this.edgeComparator); - if (edgeEq) // only if equivalent - - { - result = true; - break; - } - } - } - } - - if (result == true) { - return resultRelation; - } else { - return null; - } - } - - /** - * Will be called on every two sets of vertexes returned by the permutation - * iterator. From findNextIsomorphicGraph(). Should make sure that the two - * sets are euqivalent. Subclasses may decide to implements it as an always - * true methods only if they make sure that the permutationIterator will - * always be already equivalent. - * - * @param vertexSet1 FIXME Document me - * @param vertexSet2 FIXME Document me - */ - protected abstract boolean areVertexSetsOfTheSameEqualityGroup( - Set vertexSet1, - Set vertexSet2); - - /** - * For each edge in g1, get the Correspondence edge and test the pair. - * - * @param resultRelation - * @param edgeComparator if null, always return true. - */ - protected boolean areAllEdgesEquivalent( - IsomorphismRelation resultRelation, - EquivalenceComparator> edgeComparator) - { - boolean checkResult = true; - - if (edgeComparator == null) { - // nothing to check - return true; - } - - try { - Set edgeSet = this.graph1.edgeSet(); - - for (E currEdge : edgeSet) { - E correspondingEdge = - resultRelation.getEdgeCorrespondence(currEdge, true); - - // if one edge test fail , fail the whole method - if (!edgeComparator.equivalenceCompare( - currEdge, - correspondingEdge, - this.graph1, - this.graph2)) - { - checkResult = false; - break; - } - } - } catch (IllegalArgumentException illegal) { - checkResult = false; - } - - return checkResult; - } - - /** - * return nextElement() casted as IsomorphismRelation - */ - public IsomorphismRelation nextIsoRelation() - { - return next(); - } - - /** - * Efficiency: The value is known after the first check for isomorphism - * activated on this class and returned there after in O(1). If called on a - * new ("virgin") class, it activates 1 iso-check. - * - * @return true iff the two graphs are isomorphic - */ - public boolean isIsomorphic() - { - return !(this.nextSupplier.isEnumerationStartedEmpty()); - } - - /* (non-Javadoc) - * @see java.util.Enumeration#hasMoreElements() - */ - public boolean hasNext() - { - boolean result = this.nextSupplier.hasMoreElements(); - - return result; - } - - /** - * @see java.util.Iterator#next() - */ - public IsomorphismRelation next() - { - return this.nextSupplier.nextElement(); - } - - /* (non-Javadoc) - * @see java.util.Iterator#remove() - */ - public void remove() - { - throw new UnsupportedOperationException( - "remove() method is not supported in AdaptiveIsomorphismInspectorFactory." - + " There is no meaning to removing an isomorphism result."); - } - - //~ Inner Classes ---------------------------------------------------------- - - private class NextFunctor - implements PrefetchIterator.NextElementFunctor - { - public IsomorphismRelation nextElement() - throws NoSuchElementException - { - IsomorphismRelation resultRelation = findNextIsomorphicGraph(); - if (resultRelation != null) { - return resultRelation; - } else { - throw new NoSuchElementException( - "IsomorphismInspector does not have any more elements"); - } - } - } -} - -// End AbstractExhaustiveIsomorphismInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AdaptiveIsomorphismInspectorFactory.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AdaptiveIsomorphismInspectorFactory.java deleted file mode 100644 index 1fca2855941..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/AdaptiveIsomorphismInspectorFactory.java +++ /dev/null @@ -1,268 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * AdaptiveIsomorphismInspectorFactory.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: AdaptiveIsomorphismInspectorFactory.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.graph.*; - - -/** - * This class serves as a factory for GraphIsomorphismInspector concrete - * implementations. It can be used in two ways: - *

  • You can can let this class to determine what is the most efficient - * algorithm for your graph. - *
  • You can specify the type of your graph (planar / tree / other) and save - * this class the graph-checking time. - * - *

    Note that the concrete implementations are package-private and should not - * be created directly. If you are the maintainer of the package, you can add - * new implementation classes, and add them to the "check-list". The current - * algorithms do not support graphs with multiple edges (Multigraph / - * Pseudograph) - * - * @author Assaf - * @see GraphIsomorphismInspector - * @since Jul 17, 2005 - */ -public class AdaptiveIsomorphismInspectorFactory -{ - //~ Static fields/initializers --------------------------------------------- - - public static final int GRAPH_TYPE_ARBITRARY = 0; - public static final int GRAPH_TYPE_PLANAR = 1; - public static final int GRAPH_TYPE_TREE = 2; - public static final int GRAPH_TYPE_MULTIGRAPH = 3; - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a new inspector, letting this class determine what is the most - * efficient algorithm. - * - * @param graph1 - * @param graph2 - * @param vertexChecker may be null - * @param edgeChecker may be null - */ - public static GraphIsomorphismInspector createIsomorphismInspector( - Graph graph1, - Graph graph2, - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - int graphType = checkGraphsType(graph1, graph2); - return createAppropriateConcreteInspector( - graphType, - graph1, - graph2, - vertexChecker, - edgeChecker); - } - - /** - * Creates a new inspector, letting this class determine what is the most - * efficient algorithm and using default equivalence comparators. - * - *

    same as calling createIsomorphismInspector(graph1,graph2,null,null); - * - * @param graph1 - * @param graph2 - */ - public static GraphIsomorphismInspector createIsomorphismInspector( - Graph graph1, - Graph graph2) - { - return createIsomorphismInspector(graph1, graph2, null, null); - } - - /** - * Creates a new inspector for a particular graph type (planar / tree / - * other). - * - * @param type - AdaptiveIsomorphismInspectorFactory.GRAPH_TYPE_XXX - * @param graph1 - * @param graph2 - * @param vertexChecker - can be null - * @param edgeChecker - can be null - */ - public static GraphIsomorphismInspector - createIsomorphismInspectorByType( - int type, - Graph graph1, - Graph graph2, - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - return createAppropriateConcreteInspector( - type, - graph1, - graph2, - vertexChecker, - edgeChecker); - } - - /** - * Creates a new inspector for a particular graph type (planar / tree / - * other) using default equivalence comparators. - * - *

    same as calling - * createAppropriateConcreteInspector(graph1,graph2,null,null); - * - * @param type - AdaptiveIsomorphismInspectorFactory.GRAPH_TYPE_XXX - * @param graph1 - * @param graph2 - */ - public static GraphIsomorphismInspector - createIsomorphismInspectorByType( - int type, - Graph graph1, - Graph graph2) - { - return createAppropriateConcreteInspector( - type, - graph1, - graph2, - null, - null); - } - - /** - * Checks the graph type, and accordingly decides which type of concrete - * inspector class to create. This implementation creates an exhaustive - * inspector without further tests, because no other implementations are - * available yet. - * - * @param graph1 - * @param graph2 - * @param vertexChecker - * @param edgeChecker - */ - protected static GraphIsomorphismInspector - createAppropriateConcreteInspector( - int graphType, - Graph graph1, - Graph graph2, - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - assertUnsupportedGraphTypes(graph1); - assertUnsupportedGraphTypes(graph2); - GraphIsomorphismInspector currentInspector = null; - - switch (graphType) { - case GRAPH_TYPE_PLANAR: - case GRAPH_TYPE_TREE: - case GRAPH_TYPE_ARBITRARY: - currentInspector = - createTopologicalExhaustiveInspector( - graph1, - graph2, - vertexChecker, - edgeChecker); - break; - - default: - - throw new IllegalArgumentException( - "The type was not one of the supported types."); - } - return currentInspector; - } - - /** - * Checks if one of the graphs is from unsupported graph type and throws - * IllegalArgumentException if it is. The current unsupported types are - * graphs with multiple-edges. - * - * @param graph1 - * @param graph2 - * - * @throws IllegalArgumentException - */ - protected static void assertUnsupportedGraphTypes(Graph g) - throws IllegalArgumentException - { - if ((g instanceof Multigraph) - || (g instanceof DirectedMultigraph) - || (g instanceof Pseudograph)) - { - throw new IllegalArgumentException( - "graph type not supported for the graph" + g); - } - } - - protected static int checkGraphsType(Graph graph1, Graph graph2) - { - return GRAPH_TYPE_ARBITRARY; - } - - /** - * @return ExhaustiveInspector, where the equivalence comparator is chained - * with a topological comparator. This implementation uses: - *

  • vertex degree size comparator - */ - @SuppressWarnings("unchecked") - protected static GraphIsomorphismInspector - createTopologicalExhaustiveInspector( - Graph graph1, - Graph graph2, - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - VertexDegreeEquivalenceComparator degreeComparator = - new VertexDegreeEquivalenceComparator(); - EquivalenceComparatorChain> vertexChainedChecker = - new EquivalenceComparatorChainBase>( - degreeComparator); - vertexChainedChecker.appendComparator(vertexChecker); - - GraphIsomorphismInspector inspector = - - // FIXME hb060208 I don't understand how to generify this, yet - new EquivalenceIsomorphismInspector( - graph1, - graph2, - vertexChainedChecker, - edgeChecker); - return inspector; - } -} - -// End AdaptiveIsomorphismInspectorFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/EquivalenceIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/EquivalenceIsomorphismInspector.java deleted file mode 100644 index 9a236469e48..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/EquivalenceIsomorphismInspector.java +++ /dev/null @@ -1,325 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceIsomorphismInspector.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: EquivalenceIsomorphismInspector.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.experimental.permutation.*; - - -/** - * The current implementation uses the vertexComparator to greatly increase the - * test speed by dividing the vertexes into equivalent groups and permuting - * inside them only. The EdgeComparator is used to test edges, but not to make a - * finer division, thus it adds overhead. Use it only when needed. - * - * @author Assaf - * @since Jul 29, 2005 - */ -class EquivalenceIsomorphismInspector - extends AbstractExhaustiveIsomorphismInspector -{ - //~ Constructors ----------------------------------------------------------- - - /** - * @param graph1 - * @param graph2 - * @param vertexChecker eq. group checker for vertexes. If null, - * UniformEquivalenceComparator will be used as default (always return true) - * @param edgeChecker eq. group checker for edges. If null, - * UniformEquivalenceComparator will be used as default (always return true) - */ - public EquivalenceIsomorphismInspector( - Graph graph1, - Graph graph2, - - // XXX hb 060128: FOllowing parameter may need Graph - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - super(graph1, graph2, vertexChecker, edgeChecker); - } - - /** - * Constructor which uses the default comparators. - * - * @see ExhaustiveIsomorphismInspector(Graph,Graph,EquivalenceComparator,EquivalenceComparator) - */ - public EquivalenceIsomorphismInspector( - Graph graph1, - Graph graph2) - { - super(graph1, graph2); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates the permutation iterator according to equivalance class. - * - *

    1. Get the eq.group (ordered by size) array of the source vertex set - * (vertexSet1) - * - *

    2. Get the eq.group ordered array of vertexSet2. - * - *

    3. Reorder the second array to match the group order of the first - * array sets. 4. Use CompoundPermutationIter (and not regular - * IntegerPermutationIter) to permute only inside groups. - * - *

    - *

    That's it. If the eq.group comaparator is strong enough to provide - * small groups, this algortihm will produce a small possible permutations - * numbers. example: G1: [A,B,F,X,Y] [A->B,B->X,X->Y] - * - *

    G2: [D,Z,C,U,F] [D->C,Z->C,U->Z] - * - *

    vertexEq: three groups , one all letters A-E , second all letters S-Z - * , third the letter 'f'. 1. [(f)size=1, (X,Y)size=2 , (A,B)size=2] 2. - * [(f)size=1 ,(C,D)size=2 , (Z,U)size=2] 3. the match is done by reordering - * the second array to have the equiviavlant order :##[(f)size=1 , - * (Z,U)size=2 , (C,D)size=2]## 4.for example G2 will not do all 5!=120 - * permutations , but 2!x2!x1!=4 permutations only which are: (of the 3rd - * array) [ F, Z , U , C , D ] [ F, Z , U , D , C ] [ F, U , Z, C , D ] [ F, - * U , Z , D , C ] - * - * @return null, if the eq.group do not match (there cannot be any - * permutation for eq.groups) or the sets do not match in size; otherwise, - * the permutationiterator otherwise - * - * @see AbstractExhaustiveIsomorphismInspector#createPermutationIterator(Set, - * Set) - */ - @SuppressWarnings("unchecked") - protected CollectionPermutationIter createPermutationIterator( - Set vertexSet1, - Set vertexSet2) - { - if (vertexSet1.size() != vertexSet2.size()) { - // throw new IllegalArgumentException("the two vertx-sets - // parameters must be of" - // +"the same size. The first size was:"+vertexSet1.size() - // +" the other size was:" +vertexSet2.size() ); - return null; // only instead of exception - } - - // 1// - EquivalenceSet [] eqGroupArray1 = - EquivalenceSetCreator.createEqualityGroupOrderedArray( - vertexSet1, - this.vertexComparator, - this.graph1); - - // 2// - EquivalenceSet [] eqGroupArray2 = - EquivalenceSetCreator.createEqualityGroupOrderedArray( - vertexSet2, - this.vertexComparator, - this.graph2); - - // 3// - boolean reorderSuccess = - reorderTargetArrayToMatchSourceOrder(eqGroupArray1, eqGroupArray2); // 2 is the target - if (!reorderSuccess) { - // if reordering fail , no match can be done - return null; - } - - // reorder set1 (source), so when we work with the flat array of the - // second array, - // the permutations will be relevant. - // note that it does not start in any way related to eqGroup sizes. - - V [] reorderingVertexSet1Temp = (V []) new Object[vertexSet1.size()]; - fillElementsflatArray(eqGroupArray1, reorderingVertexSet1Temp); - vertexSet1.clear(); - vertexSet1.addAll(Arrays.asList(reorderingVertexSet1Temp)); - - // 4//use CompoundPermutationIter to permute only inside groups. - // the CollectionPermutationIter needs a array/set of objects and a - // permuter which will - // work on that set/array order. lets make these two: - // 1. create array of the vertexes , by flattening the eq.group array - // contents - - V [] flatVertexArray = (V []) new Object[vertexSet2.size()]; - fillElementsflatArray(eqGroupArray2, flatVertexArray); - - // 2. make the permuter according to the groups size - int [] groupSizesArray = new int[eqGroupArray1.length]; - - // iterate over the EqualityGroup array - for ( - int eqGroupCounter = 0; - eqGroupCounter < eqGroupArray2.length; - eqGroupCounter++) - { - // now for (.2.) size count - groupSizesArray[eqGroupCounter] = - eqGroupArray2[eqGroupCounter].size(); - } - - ArrayPermutationsIter arrayPermIter = - PermutationFactory.createByGroups(groupSizesArray); - CollectionPermutationIter vertexPermIter = - new CollectionPermutationIter( - Arrays.asList(flatVertexArray), - arrayPermIter); - - return vertexPermIter; - } - - /** - * Reorders inplace targetArray - * - *

    rules: - *

  • try to match only group of the same size and then hashcode - *
  • it is enough to choose one from each group to see if a match exist. - * - *

    Algorithm: hold counters in the two arrays. [a,b,c,d,e] assume groups - * are:a,(b,c,d),e [a,c,d,b,e] c1=0 , c2=0 check if eqvivalent . if not , - * advance , as long as both size and hashcode are the same. if found a - * match , swap the group positions in array2. if not , throws - * IllegalArgumentExcpetion. Assumption: array size is the same. not - * checked. - * - * @param sourceArray - * @param targetArray - * - * @return true if the array was reordered successfully. false if not(It - * will happen if there is no complete match between the groups) - */ - private boolean reorderTargetArrayToMatchSourceOrder( - EquivalenceSet [] sourceArray, - EquivalenceSet [] targetArray) - { - boolean result = true; - for ( - int sourceIndex = 0; - sourceIndex < sourceArray.length; - sourceIndex++) - { - int currTargetIndex = sourceIndex; - - // if they are already equivalent do nothing. - EquivalenceSet sourceEqGroup = sourceArray[sourceIndex]; - EquivalenceSet targetEqGroup = targetArray[currTargetIndex]; - if (!sourceEqGroup.equals(targetEqGroup)) { - // iterate through the next group in the targetArray until - // a new size or hashcode is seen - boolean foundMatch = false; - int sourceSize = sourceEqGroup.size(); - int sourceHashCode = sourceEqGroup.hashCode(); - while ( - (targetEqGroup.size() == sourceSize) - && (targetEqGroup.hashCode() == sourceHashCode) - && (currTargetIndex < (targetArray.length - 1))) - { - currTargetIndex++; - targetEqGroup = targetArray[currTargetIndex]; - if (targetEqGroup.equals(sourceEqGroup)) { - foundMatch = true; - - // swap . targetEqGroup will serve as the temp - // variable. - targetArray[currTargetIndex] = targetArray[sourceIndex]; - targetArray[sourceIndex] = targetEqGroup; - } - } - if (!foundMatch) { - // a match was not found - // throw new IllegalArgumentException("could not reorder - // the array , because the groups don`t match"); - result = false; - break; - } - } - } - return result; - } - - /** - * @param eqGroupArray - * @param flatArray an empy array with the proper size - */ - protected void fillElementsflatArray( - EquivalenceSet [] eqGroupArray, - Object [] flatVertexArray) - { - int flatVertexArrayNextFree = 0; // the next free place in the array - - // iterate over the EqualityGroup array - for ( - int eqGroupCounter = 0; - eqGroupCounter < eqGroupArray.length; - eqGroupCounter++) - { - Object [] currGroupArray = eqGroupArray[eqGroupCounter].toArray(); - - // copy this small array to the free place in the big - // flatVertexArray - System.arraycopy( - currGroupArray, // src - 0, // srcPos - flatVertexArray, // dest - flatVertexArrayNextFree, // destPos - currGroupArray.length // length - ); - flatVertexArrayNextFree += currGroupArray.length; - } - } - - /** - * We know for sure, that the sets are alreay checked for equivalence , so - * it will return true without any further checks. - * - * @see AbstractExhaustiveIsomorphismInspector#areVertexSetsOfTheSameEqualityGroup( - * Set, Set) - */ - protected boolean areVertexSetsOfTheSameEqualityGroup( - Set vertexSet1, - Set vertexSet2) - { - return true; - } -} - -// End EquivalenceIsomorphismInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphIsomorphismInspector.java deleted file mode 100644 index 36afff38ef0..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphIsomorphismInspector.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * GraphIsomorphismInspector.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - - -/** - * Isomorphism Overview - * - *

    Isomorphism is the problem of testing whether two graphs are topologically - * the same. Suppose we are given a collection of graphs and must perform some - * operation on each of them. If we can identify which of the graphs are - * duplicates, they can be discarded so as to avoid redundant work. - * - *

    In Formal Math: Input description: Two graphs, G and H. Problem - * description: Find a (or all) mappings f of the vertices of G to the - * vertices of H such that G and H are identical; i.e. (x,y) is an edge of G iff - * (f(x),f(y)) is an edge of H. - * http://www2.toki.or.id/book/AlgDesignManual/BOOK/BOOK4/NODE180.HTM. - * - *

    Efficiency: The general algorithm is not polynomial, however - * polynomial algorithms are known for special cases, like acyclic graphs, - * planar graphs etc. There are several heuristic algorithms which gives quite - * good results (polynomial) in general graphs, for most but not all cases. - * - *

    Usage: - * - *

      - *
    1. Choose comparators for the vertexes and edges. You may use the default - * comparator by sending null parameters for them to the constructor. Example: - * Assume Your graphs are of human relations. Each vertex is either a man or a - * woman and also has the person name. You may decide that isomorphism is - * checked according to gender, but not according to the specific name. So you - * will create a comparator that distinguishes vertexes only according to - * gender. - *
    2. Use the isIsomorphic() method as a boolean test for isomorphism - *
    3. Use the Iterator interface to iterate through all the possible - * isomorphism ordering. - *
    - * - * @author Assaf Lehr - * @since Jul 15, 2005 - */ -// REVIEW jvs 5-Sept-2005: Since we're using JDK1.5 now, we should be -// able to declare this as Iterator, correct? Otherwise -// the caller doesn't even know what they're getting back. -public interface GraphIsomorphismInspector - extends Iterator -{ - //~ Methods ---------------------------------------------------------------- - - /** - * @return true iff the two graphs are isomorphic - */ - public boolean isIsomorphic(); -} - -// End GraphIsomorphismInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphOrdering.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphOrdering.java deleted file mode 100644 index 5aa3333606b..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/GraphOrdering.java +++ /dev/null @@ -1,232 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * GraphOrdering.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Holds graph information as int labels only. vertexes: 1,2,3,4 edges:1->2 , - * 3->4 ,1->1. Implementation as imutable graph by int[] for vetexes and - * LabelsEdge[] for edges. The current algorithms do not support graph with - * multiple edges (Multigraph / Pseudograph). For the maintaner: The reason for - * it is the use of edges sets of LabelsEdge in which the equals checks for - * source and target vertexes. Thus there cannot be two LabelsEdge with the same - * source and target in the same Set. - * - * @author Assaf - * @since May 20, 2005 - */ -public class GraphOrdering -{ - //~ Instance fields -------------------------------------------------------- - - /** - * 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; - - //~ Constructors ----------------------------------------------------------- - - /** - * 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 GraphOrdering(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 GraphOrdering( - Graph regularGraph, - Set vertexSet, - Set edgeSet) - { - init(regularGraph, vertexSet, edgeSet); - } - - //~ Methods ---------------------------------------------------------------- - - 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 = new HashMap(vertexSet.size()); - - int counter = 0; - for (V vertex : vertexSet) { - mapVertexToOrder.put(vertex, new Integer(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 = new HashSet(edgeSet.size()); - for (E edge : edgeSet) { - V sourceVertex = g.getEdgeSource(edge); - Integer sourceOrder = mapVertexToOrder.get(sourceVertex); - int sourceLabel = sourceOrder.intValue(); - int targetLabel = - (mapVertexToOrder.get(g.getEdgeTarget(edge))).intValue(); - - LabelsEdge lablesEdge = new LabelsEdge(sourceLabel, targetLabel); - this.labelsEdgesSet.add(lablesEdge); - - if (g instanceof UndirectedGraph) { - LabelsEdge oppositeEdge = - new LabelsEdge(targetLabel, sourceLabel); - this.labelsEdgesSet.add(oppositeEdge); - } - } - } - - /** - * Tests equality by order of edges - */ - public boolean equalsByEdgeOrder(GraphOrdering otherGraph) - { - boolean result = - this.getLabelsEdgesSet().equals(otherGraph.getLabelsEdgesSet()); - - return result; - } - - public Set getLabelsEdgesSet() - { - return labelsEdgesSet; - } - - /** - * This is the format example: - * - *
    -       mapVertexToOrder=        labelsOrder=
    -     * 
    - */ - public String toString() - { - StringBuffer sb = new StringBuffer(); - 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.intValue()] = currVertex; - } - sb.append(Arrays.toString(vertexArray)); - sb.append("labelsOrder=").append(this.labelsEdgesSet.toString()); - return sb.toString(); - } - - //~ Inner Classes ---------------------------------------------------------- - - private 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 = - new String(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) - */ - public boolean equals(Object obj) - { - LabelsEdge otherEdge = (LabelsEdge) obj; - if ((this.source == otherEdge.source) - && (this.target == otherEdge.target)) - { - return true; - } else { - return false; - } - } - - /** - * @see java.lang.Object#hashCode() - */ - public int hashCode() - { - return this.hashCode; // filled on constructor - } - - public String toString() - { - return this.source + "->" + this.target; - } - } -} - -// End GraphOrdering.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/IsomorphismRelation.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/IsomorphismRelation.java deleted file mode 100644 index 1d5fb29d4d7..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/IsomorphismRelation.java +++ /dev/null @@ -1,151 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * IsomorphismRelation.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * Holds an isomorphism relation for two graphs. It contains a mapping between - * the two graphs. - * - *

    Usage: - * - *

      - *
    1. use getVertexCorrespondence() or - * getEdgeCorrespondence() to get the mapped object in the other graph. - *
    - * - *

    - *

    It consists of two vertexes array , the i-th vertex in the 1st array is - * the isomorphic eqv. of the i-th in 2nd array. Note that the getters are - * unsafe (they return the array and not a copy of it). - * - * @author Assaf - * @since May 27, 2005 - */ -public class IsomorphismRelation - implements GraphMapping -{ - //~ Instance fields -------------------------------------------------------- - - private List vertexList1; - private List vertexList2; - - private GraphMapping graphMapping = null; - - private Graph graph1; - private Graph graph2; - - //~ Constructors ----------------------------------------------------------- - - /** - */ - public IsomorphismRelation( - List aGraph1vertexArray, - List aGraph2vertexArray, - Graph g1, - Graph g2) - { - this.vertexList1 = aGraph1vertexArray; - this.vertexList2 = aGraph2vertexArray; - this.graph1 = g1; - this.graph2 = g2; - } - - //~ Methods ---------------------------------------------------------------- - - public String toString() - { - StringBuffer sb = new StringBuffer(); - sb.append("vertexList1: ").append( - this.vertexList1.toString()); - sb.append("\tvertexList2: ").append( - this.vertexList2.toString()); - return sb.toString(); - } - - public V getVertexCorrespondence(V vertex, boolean forward) - { - // lazy initializer for graphMapping - if (graphMapping == null) { - initGraphMapping(); - } - - return graphMapping.getVertexCorrespondence(vertex, forward); - } - - public E getEdgeCorrespondence(E edge, boolean forward) - { - // lazy initializer for graphMapping - if (graphMapping == null) { - initGraphMapping(); - } - - return graphMapping.getEdgeCorrespondence(edge, forward); - } - - /** - * We currently have the vertexes array. From them we will construct two - * maps: g1ToG2 and g2ToG1, using the array elements with the same index. - */ - private void initGraphMapping() - { - int mapSize = vertexList1.size(); - Map g1ToG2 = new HashMap(mapSize); - Map g2ToG1 = new HashMap(mapSize); - - for (int i = 0; i < mapSize; i++) { - V source = this.vertexList1.get(i); - V target = this.vertexList2.get(i); - g1ToG2.put(source, target); - g2ToG1.put(target, source); - } - this.graphMapping = - new DefaultGraphMapping( - g1ToG2, - g2ToG1, - this.graph1, - this.graph2); - } -} - -// End IsomorphismRelation.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/PermutationIsomorphismInspector.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/PermutationIsomorphismInspector.java deleted file mode 100644 index c8ad67dfdcf..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/PermutationIsomorphismInspector.java +++ /dev/null @@ -1,148 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * PermutationIsomorphismInspector.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: PermutationIsomorphismInspector.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.experimental.permutation.*; - - -/** - * Checks every possible permutation. - * - *

    It does not uses the graph topology to enhance the performance. It is - * recommended to use only if there cannot be a useful division into equivalence - * sets. - * - * @author Assaf - * @since Jul 29, 2005 - */ -class PermutationIsomorphismInspector - extends AbstractExhaustiveIsomorphismInspector -{ - //~ Constructors ----------------------------------------------------------- - - /** - * @param graph1 - * @param graph2 - * @param vertexChecker eq. group checker for vertexes. If null, - * UniformEquivalenceComparator will be used as default (always return true) - * @param edgeChecker eq. group checker for edges. If null, - * UniformEquivalenceComparator will be used as default (always return true) - */ - public PermutationIsomorphismInspector( - Graph graph1, - Graph graph2, - - // XXX hb 060128: FOllowing parameter may need Graph - EquivalenceComparator> vertexChecker, - EquivalenceComparator> edgeChecker) - { - super(graph1, graph2, vertexChecker, edgeChecker); - } - - /** - * Constructor which uses the default comparators. - * - * @see AbstractExhaustiveIsomorphismInspector#AbstractExhaustiveIsomorphismInspector(Graph, - * Graph) - */ - public PermutationIsomorphismInspector( - Graph graph1, - Graph graph2) - { - super(graph1, graph2); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates the permutation iterator, not dependant on equality group, or the - * other vertexset. - * - * @param vertexSet1 FIXME Document me - * @param vertexSet2 FIXME Document me - * - * @return the permutation iterator - */ - protected CollectionPermutationIter createPermutationIterator( - Set vertexSet1, - Set vertexSet2) - { - return new CollectionPermutationIter(vertexSet2); - } - - /** - * FIXME Document me FIXME Document me - * - * @param vertexSet1 FIXME Document me - * @param vertexSet2 FIXME Document me - * - * @return FIXME Document me - */ - protected boolean areVertexSetsOfTheSameEqualityGroup( - Set vertexSet1, - Set vertexSet2) - { - if (vertexSet1.size() != vertexSet2.size()) { - return false; - } - Iterator iter2 = vertexSet2.iterator(); - - // only check hasNext() of one , cause they are of the same size - for (Iterator iter1 = vertexSet1.iterator(); iter1.hasNext();) { - V vertex1 = iter1.next(); - V vertex2 = iter2.next(); - if (!this.vertexComparator.equivalenceCompare( - vertex1, - vertex2, - this.graph1, - this.graph2)) - { - return false; - } - } - return true; - } -} - -// End PermutationIsomorphismInspector.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/VertexDegreeEquivalenceComparator.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/VertexDegreeEquivalenceComparator.java deleted file mode 100644 index 7275a534635..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/VertexDegreeEquivalenceComparator.java +++ /dev/null @@ -1,178 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * VertexDegreeEquivalenceComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: VertexDegreeEquivalenceComparator.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; - - -/** - * Two vertexes are equivalent under this comparator if and only if: - * - *

      - *
    1. they have the same IN degree - * - *

      AND - *

    2. they have the same OUT degree - *
    - * - * @author Assaf - * @since Jul 21, 2005 - */ -public class VertexDegreeEquivalenceComparator - implements EquivalenceComparator> -{ - //~ Constructors ----------------------------------------------------------- - - /** - */ - public VertexDegreeEquivalenceComparator() - { - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Compares the in degrees and the out degrees of the two vertexes. - * - *

    One may reside in an Undirected Graph and the other in a Directed - * graph, or both on the same graph type. - * - * @see EquivalenceComparator#equivalenceCompare(Object, Object, Object, - * Object) - */ - public boolean equivalenceCompare( - V vertex1, - V vertex2, - Graph context1, - Graph context2) - { - // note that VertexDegreeComparator cannot be used. It supports only - // directed graphs. - InOutDegrees inOut1 = getInOutDegrees(context1, vertex1); - InOutDegrees inOut2 = getInOutDegrees(context2, vertex2); - boolean result = inOut1.equals(inOut2); - return result; - } - - /** - * Hashes using the in & out degree of a vertex - * - * @see EquivalenceComparator#equivalenceHashcode(Object, Object) - */ - public int equivalenceHashcode(V vertex, Graph context) - { - InOutDegrees inOut = getInOutDegrees(context, vertex); - - // hash it using the string hash. use the format N '-' N - StringBuffer sb = new StringBuffer(); - sb.append(String.valueOf(inOut.inDegree)); - sb.append("-"); // to diffrentiate inner and outer - sb.append(String.valueOf(inOut.outDegree)); - return sb.toString().hashCode(); - } - - /** - * Calculates the In and Out degrees of vertexes. Supported graph types: - * UnDirectedGraph, DirectedGraph. In UnDirected graph, the in = out (as if - * it was undirected and every edge is both an in and out edge) - * - * @param aContextGraph - * @param vertex - */ - protected InOutDegrees getInOutDegrees(Graph aContextGraph, - V vertex) - { - int inVertexDegree = 0; - int outVertexDegree = 0; - if (aContextGraph instanceof UndirectedGraph) { - UndirectedGraph undirectedGraph = - (UndirectedGraph) aContextGraph; - inVertexDegree = undirectedGraph.degreeOf(vertex); - outVertexDegree = inVertexDegree; // it is UNdirected - } else if (aContextGraph instanceof DirectedGraph) { - DirectedGraph directedGraph = - (DirectedGraph) aContextGraph; - inVertexDegree = directedGraph.inDegreeOf(vertex); - outVertexDegree = directedGraph.outDegreeOf(vertex); - } else { - throw new RuntimeException( - "contextGraph is of unsupported type . It must be one of these two :" - + " UndirectedGraph or DirectedGraph"); - } - return new InOutDegrees(inVertexDegree, outVertexDegree); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * Simple structure used to hold the two ints: vertex in degree and vertex - * out degree. Useful as returned value for methods which calculate both at - * the same time. - * - * @author Assaf - * @since Jul 21, 2005 - */ - protected class InOutDegrees - { - public int inDegree; - public int outDegree; - - public InOutDegrees(int aInDegree, int aOutDegree) - { - this.inDegree = aInDegree; - this.outDegree = aOutDegree; - } - - /** - * Checks both inDegree and outDegree. Does not check class type to save - * time. If should be used with caution. - * - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object obj) - { - InOutDegrees other = (InOutDegrees) obj; - return ((this.inDegree == other.inDegree) - && (this.outDegree == other.outDegree)); - } - } -} - -// End VertexDegreeEquivalenceComparator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/package.html b/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/package.html deleted file mode 100644 index 07c613fe19b..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/isomorphism/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Algorithms which provide isomorphism check between two graphs. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/package.html b/jgrapht-core/src/main/java/org/jgrapht/experimental/package.html deleted file mode 100644 index 2a72e67640f..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/package.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -

    A package that contains experimental work or work-in-progress that -is not yet ready to be included in a release. It may contain classes -that are: incomplete, not yet documented, have not yet reached a -satisfying form, etc.

    - -

    The only requirement for classes included here is to compile.

    - - \ No newline at end of file diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/ArrayPermutationsIter.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/ArrayPermutationsIter.java deleted file mode 100644 index 6d350bb73f4..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/ArrayPermutationsIter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * ArrayPermutationsIter.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -/** - * An interface to iterate over array permutations. Similiar to Iterator, but - * with specific return types and without the remove() method. - * - * @author Assaf - * @since Jul 29, 2005 - */ -public interface ArrayPermutationsIter -{ - //~ Methods ---------------------------------------------------------------- - - public int [] nextPermutation(); - - public boolean hasNextPermutaions(); -} - -// End ArrayPermutationsIter.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CollectionPermutationIter.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CollectionPermutationIter.java deleted file mode 100644 index 11e8071adb6..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CollectionPermutationIter.java +++ /dev/null @@ -1,185 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * CollectionPermutationIter.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -import java.util.*; - - -/** - * Given a container with elements (Collection,Enumeration,array) defines a - * permutation iterator which returns, on each iteration, a differnt permutation - * of the source container. You may choose a different container - * type(Collection/Array/etc) for each next call. It will continue as if they - * were the same iterator. - * - * @author Assaf - * @since May 20, 2005 - */ -public class CollectionPermutationIter -{ - //~ Instance fields -------------------------------------------------------- - - private ArrayPermutationsIter permOrder; - private List sourceArray; - - /** - * change everry calculation.can be retrieved publicly - */ - private int [] currPermutationArray; - - //~ Constructors ----------------------------------------------------------- - - /** - * Note: the Set interface does not guarantee iteration order. This method - * iterates on the set to get the initial order and after that the data will - * be saved internally in another (ordered) container. So, remeber that the - * Initial order can be different from the objectSet.toString() method. If - * you want it to be the same, use a LinkedHashSet , or use the array - * constructor. - * - * @param objectsSet - */ - public CollectionPermutationIter(Set objectsSet) - { - this( - new ArrayList(objectsSet), - new IntegerPermutationIter(objectsSet.size())); - } - - /** - * Uses a permArray like [1,1,1,2] where some of the permutations are not - * relevant. Here there will be 4 permutations (only the '2' position is - * important) - * - * @param objectsArray - * @param permuter - */ - public CollectionPermutationIter(List objectsArray) - { - this( - objectsArray, - new IntegerPermutationIter(objectsArray.size())); - } - - public CollectionPermutationIter( - List objectsArray, - ArrayPermutationsIter permuter) - { - this.permOrder = permuter; - this.sourceArray = objectsArray; - } - - //~ Methods ---------------------------------------------------------------- - - public boolean hasNext() - { - return this.permOrder.hasNextPermutaions(); - } - - /** - * On first call, returns the source as an array; on any other call - * thereafter, a new permutation - * - * @return null if we overflowed! the array otherwise - */ - public List getNextArray() - { - List permutationResult; // will hold the array result - if (this.permOrder.hasNextPermutaions()) { - this.currPermutationArray = this.permOrder.nextPermutation(); - permutationResult = applyPermutation(); - } else { - permutationResult = null; - } - - return permutationResult; - } - - private List applyPermutation() - { - ArrayList output = new ArrayList(sourceArray); - - // Example : this.sourceArray = ["A","B","C","D"] - // perOrder: = [ 1 , 0 , 3 , 2 ] - // result : = ["B","A","D","C"] - for (int i = 0; i < output.size(); i++) { - output.set( - i, - this.sourceArray.get(this.currPermutationArray[i])); - } - return output; - } - - /** - * Wrap result to a Set. - * - * @return null if we overflowed! the set otherwise - */ - public Set getNextSet() - { - List result = getNextArray(); - if (result == null) { - return null; - } else // wrap in a SET - { - Set resultSet = new LinkedHashSet(result); - return resultSet; - } - } - - public int [] getCurrentPermutationArray() - { - return this.currPermutationArray; - } - - public String toString() - { - StringBuffer sb = new StringBuffer(); - sb.append("Permutation int[]="); - sb.append(Arrays.toString(getCurrentPermutationArray())); - - List permutationResult = applyPermutation(); - sb.append("\nPermutationSet Source Object[]="); - sb.append(this.sourceArray.toString()); - sb.append("\nPermutationSet Result Object[]="); - sb.append(permutationResult.toString()); - return sb.toString(); - } -} - -// End CollectionPermutationIter.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CompoundPermutationIter.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CompoundPermutationIter.java deleted file mode 100644 index c9886982fd3..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/CompoundPermutationIter.java +++ /dev/null @@ -1,307 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * CompoundPermutationIter.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -import java.util.*; - -import org.jgrapht.util.*; - - -/** - * For permutation like this: - *
  • 1,2 are the same eq.group (numbers) - *
  • a,b are og the same eq.group (letters) - *
  • '$' is of its own eq. group (signs) Let the order of the group be - * (arbitrary): signs,numbers,letters (note that for performance reasons, this - * arbitrary order is the worst! see Performance section below) - * - *

    These are the possible compound perm: [$,1,2,a,b,c] - * - *

    [$,1,2,a,c,b] - * - *

    [$,1,2,b,a,c] - * - *

    [$,1,2,b,c,a] - * - *

    [$,1,2,c,a,b] - * - *

    [$,1,2,c,b,a] - * - *

    [$,2,1,a,b,c] - * - *

    [$,2,1,a,c,b] - * - *

    [$,2,1,b,a,c] - * - *

    [$,2,1,b,c,a] - * - *

    [$,2,1,c,a,b] - * - *

    [$,2,1,c,b,a] - * - *

    The overall number is the product of the factorials of each eq. group - * size; in our example : (1!)x(2!)x(3!)=1x2x6=12. Using the constructor with - * eq.group sizes and initial order [1,2,3], the result permutations are - * retrieved as numbers in an array, where [0,1,2,3,4,5] means [$,1,2,a,b,c]: - * - *

    [0,1,2,3,5,4] - * - *

    [0,1,2,4,3,5] - * - *

    etc. etc., till: - * - *

    [0,2,1,5,4,3] means [$,2,1,c,b,a] - * - *

    - *

    Performance: The implementation tries to advance each time the - * group zero, if it does not succeed, it tries the next group (1,2 and so on), - * so: try to put the largest group as the first groups, UNLIKE the example. - * Performance-wise it is better to do [a,b,c,1,2,$] .The effect is improvement - * by constant (for example, by 2) - * - * @author Assaf - * @since May 30, 2005 - */ -public class CompoundPermutationIter - implements ArrayPermutationsIter, - Iterator -{ - //~ Instance fields -------------------------------------------------------- - - IntegerPermutationIter [] permArray; - - /** - * on the example 1+2+3=6 - */ - private int totalPermArraySize; - - /** - * The overall number is the product of the factorial of each eq. group - * size. - */ - private int max; - - private int iterCounter = 0; - - //~ Constructors ----------------------------------------------------------- - - /** - * For the class example, use [1,2,2]. order matters! (performance-wise too) - * - * @param equalityGroupsSizesArray - */ - public CompoundPermutationIter(int [] equalityGroupsSizesArray) - { - init(equalityGroupsSizesArray); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates an IntegerPermutationIter class per equalityGroup with different - * integers. - * - * @param equalityGroupsSizesArray - */ - private void init(int [] equalityGroupsSizesArray) - { - this.permArray = - new IntegerPermutationIter[equalityGroupsSizesArray.length]; - - int counter = 0; - this.max = 1; // each time , multiply by factorail(eqGroupSize) - for ( - int eqGroup = 0; - eqGroup < equalityGroupsSizesArray.length; - eqGroup++) - { - // create an array of eq.group size filled with values - // of counter, counter+1, ... counter+size-1 - int currGroupSize = equalityGroupsSizesArray[eqGroup]; - int [] currArray = new int[currGroupSize]; - for (int i = 0; i < currGroupSize; i++) { - currArray[i] = counter; - counter++; - } - this.permArray[eqGroup] = new IntegerPermutationIter(currArray); - this.permArray[eqGroup].getNext(); // first iteration return the - // source - - // each time , multiply by factorail(eqGroupSize) - this.max *= MathUtil.factorial(currGroupSize); - } - this.totalPermArraySize = counter; - - // calc max - } - - public Object next() - { - return getNext(); - } - - /** - * Iteration may be one of these two: 1. the last group advances by one - * iter, all else stay. 2. the last group cannot advance , so it restarts - * but telling the group after it to advance (done recursively till some - * group can advance) - */ - public int [] getNext() - { - if (this.iterCounter == 0) { - // just return it , without change - this.iterCounter++; - return getPermAsArray(); - } - - int firstGroupCapableOfAdvancing = -1; - int currGroupIndex = 0; // - while (firstGroupCapableOfAdvancing == -1) { - IntegerPermutationIter currGroup = this.permArray[currGroupIndex]; - - if (currGroup.hasNext()) { - currGroup.getNext(); - - // restart all that we passed on - for (int i = 0; i < currGroupIndex; i++) { - restartPermutationGroup(i); - } - firstGroupCapableOfAdvancing = currGroupIndex; - } - - currGroupIndex++; - if (currGroupIndex >= this.permArray.length) { - break; - } - } - - this.iterCounter++; - - if (firstGroupCapableOfAdvancing == -1) { - // nothing found. we finished all iterations - return null; - } else { - int [] tempArray = getPermAsArray(); - return tempArray; - } - } - - /** - * Creates and returns a new array which consists of the eq. group current - * permutation arrays. For example, in the 10th iter ([$,2,1,b,c,a]) The - * permutations current statuses is [0] [2,1] [4,5,3] so retrieve - * [0,2,1,4,5,3] - */ - public int [] getPermAsArray() - { - int [] resultArray = new int[this.totalPermArraySize]; - int counter = 0; - for ( - int groupIndex = 0; - groupIndex < this.permArray.length; - groupIndex++) - { - int [] currPermArray = this.permArray[groupIndex].getCurrent(); - System.arraycopy( - currPermArray, - 0, - resultArray, - counter, - currPermArray.length); - counter += currPermArray.length; - } - return resultArray; - } - - /** - * Restarts by creating a new one instead. - * - * @param groupIndex - */ - private void restartPermutationGroup(int groupIndex) - { - int [] oldPermArray = this.permArray[groupIndex].getCurrent(); - Arrays.sort(oldPermArray); - this.permArray[groupIndex] = new IntegerPermutationIter(oldPermArray); - this.permArray[groupIndex].getNext(); - } - - public boolean hasNext() - { - boolean result; - if (this.iterCounter < this.max) { - result = true; - } else { - result = false; - } - return result; - } - - public int getMax() - { - return max; - } - - /* (non-Javadoc) - * @see ArrayPermutationsIter#nextPermutation() - */ - public int [] nextPermutation() - { - return (int []) next(); - } - - /* (non-Javadoc) - * @see ArrayPermutationsIter#hasNextPermutaions() - */ - public boolean hasNextPermutaions() - { - return hasNext(); - } - - /** - * UNIMPLEMENTED. always throws new UnsupportedOperationException - * - * @see java.util.Iterator#remove() - */ - public void remove() - { - throw new UnsupportedOperationException(); - } -} - -// End CompoundPermutationIter.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/IntegerPermutationIter.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/IntegerPermutationIter.java deleted file mode 100644 index 55fb14acec1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/IntegerPermutationIter.java +++ /dev/null @@ -1,311 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * IntegerPermutationIter.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -import java.util.*; - - -/** - * Iterates through permutations of N elements. - *

  • use getNext() to get the next permutation order, for example(N=4): - * perm0=[1,2,3,4] perm1=[1,2,4,3] perm2=[1,3,2,4] . - *
  • use hasNext() or verify by counter[1,1,1,2,3]; - * note that there are much less than 5! premutations here, because of the - * repetitive 1s. - * - * @param array creates a copy of it (so sort / later changes will not - * matter) - */ - public IntegerPermutationIter(int [] array) - { - int [] newArray = new int[array.length]; - System.arraycopy(array, 0, newArray, 0, array.length); - Arrays.sort(newArray); - init(newArray); - } - - //~ Methods ---------------------------------------------------------------- - - private void init(int [] array) - { - this.N = array.length; - this.Value = array; - this.currentValueBackup = this.Value; - permutationCounter = 0; - } - - /** - * Swaps by array indexes - * - * @param i - * @param j - */ - private void swap(int i, int j) - { - int temp = this.Value[i]; - this.Value[i] = this.Value[j]; - this.Value[j] = temp; - } - - private int [] arrayClone(int [] sourceArray) - { - int [] destArray = new int[sourceArray.length]; - System.arraycopy(sourceArray, 0, destArray, 0, sourceArray.length); - return destArray; - } - - private int [] getNextStartingWith2() - { - permutationCounter++; - int i = N - 1; - - if (i <= 0) // may happen only on N<=1 - - { - this.endWasReached = true; - return null; - } - - /** while (Value[i-1] >= Value[i]) - { - i = i-1; - }*/ - while (Value[i - 1] >= Value[i]) { - i = i - 1; - if (i == 0) { - this.endWasReached = true; - return null; - } - } - - int j = N; - - while (Value[j - 1] <= Value[i - 1]) { - j = j - 1; - } - - swap(i - 1, j - 1); // swap values at positions (i-1) and (j-1) - - i++; - j = N; - - while (i < j) { - swap(i - 1, j - 1); - i++; - j--; - } - return this.Value; - } - - /** - * Efficiency: O(N) implementation, try to take the next! - */ - public boolean hasNext() - { - if ((this.permutationCounter == 0) - || (this.wasNextValueCalculatedAlready)) - { - return true; - } else if (this.endWasReached) { - return false; - } - - boolean result = true; - // calculate the next value into this.value save the current result. in - // the end swap the arrays there is no way to know when to stop , but - // the out-of-bound - /* try - * { - * this.wasNextValueCalculatedAlready=true; - * getNextStartingWith2(); - * } - * catch (ArrayIndexOutOfBoundsException outOfBoundException) - * { - * endWasReached=true; - * result=false; - * }*/ - - getNextStartingWith2(); - this.wasNextValueCalculatedAlready = true; - if (endWasReached) { - return false; - } - - ////////////////////////////// - return result; - } - - public Object next() - { - return getNext(); - } - - /** - * Facade. use it with getNext. efficency: O(N) - * - * @return a new Array with the permutatation order. for example: - * perm0=[1,2,3,4] perm1=[1,2,4,3] perm2=[1,3,2,4] - */ - public int [] getNext() - { - if (!hasNext()) { - throw new RuntimeException( - "IntegerPermutationIter exceeds the total number of permutaions." - + " Suggestion: do a check with hasNext() , or count till getTotalNumberOfPermutations" - + " before using getNext()"); - } - - // if it is the first one , return original - int [] internalArray; - if (this.permutationCounter == 0) { - this.permutationCounter++; - internalArray = this.Value; - } else { - // if hasNext() has precaclulated it , take this value. - if (this.wasNextValueCalculatedAlready) { - internalArray = this.Value; - this.wasNextValueCalculatedAlready = false; - } else { - internalArray = getNextStartingWith2(); - if (this.endWasReached) { - return null; - } - } - } - this.currentValueBackup = arrayClone(internalArray); - return arrayClone(internalArray); - } - - public int [] getCurrent() - { - return arrayClone(this.currentValueBackup); - } - - /** - * Utility method to convert the array into a string examples: [] [0] - * [0,1][1,0] - * - * @param array - */ - public String toString(int [] array) - { - if (array.length <= 0) { - return "[]"; - } - StringBuffer stBuffer = new StringBuffer("["); - for (int i = 0; i < (array.length - 1); i++) { - stBuffer.append(array[i]).append(","); - } - stBuffer.append(array[array.length - 1]).append("]"); - return stBuffer.toString(); - } - - /** - * UNIMPLEMENTED. always throws new UnsupportedOperationException - * - * @see java.util.Iterator#remove() - */ - public void remove() - { - throw new UnsupportedOperationException(); - } - - /* (non-Javadoc) - * @see ArrayPermutationsIter#nextPermutation() - */ - public int [] nextPermutation() - { - return (int []) next(); - } - - /* (non-Javadoc) - * @see ArrayPermutationsIter#hasNextPermutaions() - */ - public boolean hasNextPermutaions() - { - return hasNext(); - } -} - -// End IntegerPermutationIter.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/PermutationFactory.java b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/PermutationFactory.java deleted file mode 100644 index 540df617860..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/PermutationFactory.java +++ /dev/null @@ -1,98 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * PermutationFactory.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -/** - * Factory to create Permutations of several types and use them as Enumerations. - * Note that callers may use them directly if they need to use special concrete - * methods. - * - *

    These types are: - * - *

    - *

  • All elements are different. There are N! possible permutations. - * - *

    example: source=[1,2,3] - * result=[1,2,3][1,3,2][2,1,3][2,3,1][3,1,2][3,2,1] - * - *

    - *

  • Some of the elements are the same. - * - *

    example: source=[1,1,2] result=[1,1,2][1,2,1][2,1,1] - * - *

    - *

  • There are separate permutations groups, which are connected to one - * sequence. Permutations are allowed only inside the group. Possible sequences: - * product of factorial of each group. see example. - * - *

    example: assume source=the groups are sizes are : 1,2,2,5 elements - * will be created: (1),(2,3),(4,5). - * - *

    result=[1,(2,3),(4,5)] [1,(2,3),(5,4)] [1,(3,2),(5,4)] [1,(3,2),(4,5)]. In - * this example the number of possiblities is 1! x 2! x 2! = 4 - * - * @author Assaf Lehr - * @since Jun 3, 2005 - */ -public class PermutationFactory -{ - //~ Methods ---------------------------------------------------------------- - - public static ArrayPermutationsIter createRegular(int [] permSourceArray) - { - IntegerPermutationIter regularPerm = - new IntegerPermutationIter(permSourceArray); - return regularPerm; - } - - /** - * For efficiency, try putting the biggest groups at the beggining of the - * array. - * - * @param groupSizesArray . example [3,2] will create an array (0,1,2)(3,4) - */ - public static ArrayPermutationsIter createByGroups( - int [] groupSizesArray) - { - CompoundPermutationIter complexPerm = - new CompoundPermutationIter(groupSizesArray); - return complexPerm; - } -} - -// End PermutationFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/package.html b/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/package.html deleted file mode 100644 index 82cd61fb0c3..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/experimental/permutation/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Classes to provide all the possible permutations of arrays or sets. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertForestGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertForestGenerator.java new file mode 100644 index 00000000000..39524b134ac --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertForestGenerator.java @@ -0,0 +1,155 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertGraphGenerator.java new file mode 100644 index 00000000000..d682c2a1ee8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/BarabasiAlbertGraphGenerator.java @@ -0,0 +1,166 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/ComplementGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/ComplementGraphGenerator.java new file mode 100644 index 00000000000..e3db08dbd9b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/ComplementGraphGenerator.java @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2017-2023, 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 {@code 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); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteBipartiteGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteBipartiteGraphGenerator.java index 857cf13f69b..ebc75f1f417 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteBipartiteGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteBipartiteGraphGenerator.java @@ -1,119 +1,100 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * CompleteBipartiteGraphGenerator.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. +/* + * (C) Copyright 2008-2023, by Andrew Newell and Contributors. * - * Original Author: Andrew Newell - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. + * 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 - * @since Dec 21, 2008 */ public class CompleteBipartiteGraphGenerator implements GraphGenerator { - //~ Instance fields -------------------------------------------------------- - - private int sizeA, sizeB; - - //~ Constructors ----------------------------------------------------------- + private final int sizeA, sizeB; + private final Set partitionA, partitionB; /** * Creates a new CompleteBipartiteGraphGenerator object. * - * @param partitionOne This is the number of vertices in the first partition - * @param partitionTwo This is the number of vertices in the second parition + * @param partitionA number of vertices in the first partition + * @param partitionB number of vertices in the second partition */ - public CompleteBipartiteGraphGenerator(int partitionOne, int partitionTwo) + public CompleteBipartiteGraphGenerator(int partitionA, int partitionB) { - if ((partitionOne < 0) || (partitionTwo < 0)) { - throw new IllegalArgumentException("must be non-negative"); + if (partitionA < 0 || partitionB < 0) { + throw new IllegalArgumentException("partition sizes must be non-negative"); } - this.sizeA = partitionOne; - this.sizeB = partitionTwo; + this.sizeA = partitionA; + this.sizeB = partitionB; + this.partitionA = CollectionUtil.newLinkedHashSetWithExpectedSize(sizeA); + this.partitionB = CollectionUtil.newLinkedHashSetWithExpectedSize(sizeB); } - //~ Methods ---------------------------------------------------------------- - /** - * Construct a complete bipartite graph + * 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 void generateGraph( - Graph target, - final VertexFactory vertexFactory, - Map resultMap) + public CompleteBipartiteGraphGenerator(Set partitionA, Set partitionB) { - if ((sizeA < 1) && (sizeB < 1)) { - return; + 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; + } - //Create vertices in each of the partitions - Set a = new HashSet(); - Set b = new HashSet(); + /** + * 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++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); - a.add(newVertex); + partitionA.add(target.addVertex()); } for (int i = 0; i < sizeB; i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); - b.add(newVertex); + partitionB.add(target.addVertex()); } - //Add an edge for each pair of vertices in different partitions - for (Iterator iterA = a.iterator(); iterA.hasNext();) { - V v = iterA.next(); - for (Iterator iterB = b.iterator(); iterB.hasNext();) { - target.addEdge(v, iterB.next()); + // Add an edge for each pair of vertices in different partitions + for (V u : partitionA) { + for (V v : partitionB) { + target.addEdge(u, v); } } } } - -// End CompleteBipartiteGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteGraphGenerator.java old mode 100755 new mode 100644 index 96e4cfe13a0..b1b65133529 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteGraphGenerator.java @@ -1,139 +1,103 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Tim Shearouse and Contributors. * - * 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. - */ -/* ------------------- - * CompleteGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by Tim Shearouse and Contributors. - * - * Original Author: Tim Shearouse - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Feb-2008 : Initial revision (TS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. On a side note, a - * complete graph is the least efficient possible graph in terms of memory and - * cpu usage. Note: This contructor was designed for a simple undirected or - * directed graph. It will act strangely when used with certain graph types, - * such as undirected multigraphs. Note, though, that a complete undirected - * multigraph is rather senseless -- you can keep adding edges and the graph is - * never truly complete. + * 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 - * @since Nov 02, 2008 */ public class CompleteGraphGenerator implements GraphGenerator { - //~ Instance fields -------------------------------------------------------- - - private int size; - - //~ Constructors ----------------------------------------------------------- + private final int size; /** * Construct a new CompleteGraphGenerator. * - * @param size number of vertices to be generated + * 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. * - * @throws IllegalArgumentException if the specified size is negative. + * @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("must be non-negative"); - } - + if (size < 0) + throw new IllegalArgumentException("size must be non-negative"); this.size = size; } - //~ Methods ---------------------------------------------------------------- + /** + * 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} */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { - if (size < 1) { - return; - } - - //Add all the vertices to the set - for (int i = 0; i < size; i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); - } - /* - * We want two iterators over the vertex set, one fast and one slow. - * The slow one will move through the set once. For each vertex, - * the fast iterator moves through the set, adding an edge to all - * vertices we haven't connected to yet. - * - * If we have an undirected graph, the second addEdge call will return - * nothing; it will not add a second edge. + * Ensure directed or undirected */ - Iterator slowI = target.vertexSet().iterator(); - Iterator fastI; + GraphTests.requireDirectedOrUndirected(target); + boolean isDirected = target.getType().isDirected(); - while (slowI.hasNext()) { //While there are more vertices in the set - - V latestVertex = slowI.next(); - fastI = target.vertexSet().iterator(); - - //Jump to the first vertex *past* latestVertex - while (fastI.next() != latestVertex) { - ; - } + /* + * Add vertices + */ + for (int i = 0; i < size; i++) + target.addVertex(); - //And, add edges to all remaining vertices - V temp; - while (fastI.hasNext()) { - temp = fastI.next(); - target.addEdge(latestVertex, temp); - target.addEdge(temp, latestVertex); + /* + * 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); + } } } } } - -// End CompleteGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/DirectedScaleFreeGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/DirectedScaleFreeGraphGenerator.java new file mode 100644 index 00000000000..1ba9ec89e7c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/DirectedScaleFreeGraphGenerator.java @@ -0,0 +1,498 @@ +/* + * (C) Copyright 2019-2023, 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 {@code allowingMultipleEdges} and {@code 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 {@code allowingMultipleEdges} and {@code 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/EmptyGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/EmptyGraphGenerator.java index 21d63001fb2..1da9a8b72b6 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/EmptyGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/EmptyGraphGenerator.java @@ -1,95 +1,62 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * EmptyGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - import org.jgrapht.*; +import java.util.*; /** - * Generates an empty - * graph of any size. An empty graph is a graph that has no edges. + * 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 - * @since Sep 16, 2003 */ public class EmptyGraphGenerator implements GraphGenerator { - //~ Instance fields -------------------------------------------------------- - - private int size; - - //~ Constructors ----------------------------------------------------------- + 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("must be non-negative"); + throw new IllegalArgumentException("size must be non-negative"); } - this.size = size; } - //~ Methods ---------------------------------------------------------------- - /** * {@inheritDoc} */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { for (int i = 0; i < size; ++i) { - target.addVertex(vertexFactory.createVertex()); + target.addVertex(); } } } - -// End EmptyGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GeneralizedPetersenGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GeneralizedPetersenGraphGenerator.java new file mode 100644 index 00000000000..0335097c707 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GeneralizedPetersenGraphGenerator.java @@ -0,0 +1,100 @@ +/* + * (C) Copyright 2017-2023, 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 static final String STAR = "star"; + /** + * Key used to access the regular polygon vertices in the resultMap + */ + public static 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomBipartiteGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomBipartiteGraphGenerator.java new file mode 100644 index 00000000000..8fe9fa5b9c3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomBipartiteGraphGenerator.java @@ -0,0 +1,222 @@ +/* + * (C) Copyright 2004-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomGraphGenerator.java new file mode 100644 index 00000000000..a95e5aa011a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomGraphGenerator.java @@ -0,0 +1,287 @@ +/* + * (C) Copyright 2005-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomBipartiteGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomBipartiteGraphGenerator.java new file mode 100644 index 00000000000..4f2935c1872 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomBipartiteGraphGenerator.java @@ -0,0 +1,195 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomGraphGenerator.java new file mode 100644 index 00000000000..0fa21fd729c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomGraphGenerator.java @@ -0,0 +1,185 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GraphGenerator.java index 105869ffe78..5c52b967445 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/GraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GraphGenerator.java @@ -1,80 +1,72 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * GraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - import org.jgrapht.*; +import java.util.*; /** - * GraphGenerator defines an interface for generating new graph structures. + * 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 - * @since Sep 16, 2003 */ public interface GraphGenerator { - //~ Methods ---------------------------------------------------------------- /** - * 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. + * 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 vertexFactory called to produce new vertices - * @param resultMap if non-null, receives implementation-specific mappings - * from String roles to graph elements (or collections of graph elements) + * @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 + * {@code null}. */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap); -} + void generateGraph(Graph target, Map resultMap); -// End GraphGenerator.java + /** + * 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/GridGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/GridGraphGenerator.java index adb5659f0bb..a82e933b563 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/GridGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/GridGraphGenerator.java @@ -1,78 +1,48 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2011, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2011-2023, by Assaf Mizrachi and Contributors. * - * 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. - */ -/* ------------------- - * StarGraphGenerator.java - * ------------------- - * (C) Copyright 2011-2011, by Assaf Mizrachi and Contributors. - * - * Original Author: Assaf Mizrachi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id: StarGraphGenerator.java 651 2008-12-24 21:13:41Z perfecthash $ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 3-Jan-2011 : Initial revision (AM); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. + * 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 - * @since Dec 29, 2010 */ public class GridGraphGenerator implements GraphGenerator { - //~ Static fields/initializers --------------------------------------------- - /** * Role for the vertices at the corners. */ public static final String CORNER_VERTEX = "Corner Vertex"; - //~ Instance fields -------------------------------------------------------- - - private int rows; - - private int cols; - - //~ Constructors ----------------------------------------------------------- + private final int rows; + private final int cols; /** * Creates a new GridGraphGenerator object with rows x cols dimension. @@ -84,39 +54,31 @@ public GridGraphGenerator(int rows, int cols) { if (rows < 2) { throw new IllegalArgumentException( - "illegal number of rows (" + rows - + "). there must be at least two."); + "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."); + "illegal number of columns (" + cols + "). there must be at least two."); } this.rows = rows; this.cols = cols; } - //~ Methods ---------------------------------------------------------------- - /** * {@inheritDoc} */ - @Override public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { - Map map = new TreeMap(); + 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 = vertexFactory.createVertex(); - target.addVertex(vertex); - map.put(i + 1, vertex); + 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))) + 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); @@ -128,18 +90,13 @@ public GridGraphGenerator(int rows, int cols) // 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 : map.keySet()) { - for (int j : map.keySet()) { - if ((((i % cols) > 0) - && ((i + 1) == Integer.valueOf(j))) - || ((i + cols) == j)) - { - target.addEdge(map.get(i), map.get(j)); - target.addEdge(map.get(j), map.get(i)); + 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)); } } } } } - -// End GridGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/HyperCubeGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/HyperCubeGraphGenerator.java index e58e714554b..3e6a8012996 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/HyperCubeGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/HyperCubeGraphGenerator.java @@ -1,107 +1,74 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2008-2023, by Andrew Newell and Contributors. * - * 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. - */ -/* ------------------- - * HyperCubeGraphGenerator.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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-dimensial 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. + * 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 - * @since Dec 21, 2008 */ public class HyperCubeGraphGenerator implements GraphGenerator { - //~ Instance fields -------------------------------------------------------- - private int dim; - //~ Constructors ----------------------------------------------------------- - /** - * Creates a new HyperCubeGraphGenerator object. + * Creates a new generator * - * @param dim This is the dimension of the hypercube. + * @param dim the dimension of the hypercube */ public HyperCubeGraphGenerator(int dim) { this.dim = dim; } - //~ Methods ---------------------------------------------------------------- - - /** - * This will generate the hypercube graph - */ - public void generateGraph( - Graph target, - final VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { - //Vertices are created, and they are included in the resultmap as their - //bitstring representation + // 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(); + LinkedList vertices = new LinkedList<>(); for (int i = 0; i < order; i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); + V newVertex = target.addVertex(); + vertices.add(newVertex); if (resultMap != null) { - String s = Integer.toBinaryString(i); + StringBuilder s = new StringBuilder(Integer.toBinaryString(i)); while (s.length() < dim) { - s = "0" + s; + s.insert(0, "0"); } - resultMap.put(s, newVertex); + resultMap.put(s.toString(), newVertex); } } - //Two vertices will have an edge if their bitstrings differ by exactly - //1 element + // 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++) { @@ -114,5 +81,3 @@ public void generateGraph( } } } - -// End HyberCubeGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/KleinbergSmallWorldGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/KleinbergSmallWorldGraphGenerator.java new file mode 100644 index 00000000000..aac315c64a4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/KleinbergSmallWorldGraphGenerator.java @@ -0,0 +1,222 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/LinearGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/LinearGraphGenerator.java index b0b676e5ca1..fd89ab17557 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/LinearGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/LinearGraphGenerator.java @@ -1,61 +1,38 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * LinearGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. + * 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 - * @since Sep 16, 2003 */ public class LinearGraphGenerator implements GraphGenerator { - //~ Static fields/initializers --------------------------------------------- - /** * Role for the first vertex generated. */ @@ -66,12 +43,8 @@ public class LinearGraphGenerator */ public static final String END_VERTEX = "End Vertex"; - //~ Instance fields -------------------------------------------------------- - private int size; - //~ Constructors ----------------------------------------------------------- - /** * Construct a new LinearGraphGenerator. * @@ -88,21 +61,16 @@ public LinearGraphGenerator(int size) this.size = size; } - //~ Methods ---------------------------------------------------------------- - /** * {@inheritDoc} */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { V lastVertex = null; for (int i = 0; i < size; ++i) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); + V newVertex = target.addVertex(); if (lastVertex == null) { if (resultMap != null) { @@ -120,5 +88,3 @@ public void generateGraph( } } } - -// End LinearGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/LinearizedChordDiagramGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/LinearizedChordDiagramGraphGenerator.java new file mode 100644 index 00000000000..7e2a334da27 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/LinearizedChordDiagramGraphGenerator.java @@ -0,0 +1,142 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/NamedGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/NamedGraphGenerator.java new file mode 100644 index 00000000000..77eaade5702 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/NamedGraphGenerator.java @@ -0,0 +1,1791 @@ +/* + * (C) Copyright 2017-2023, 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) + { + return vertexMap.computeIfAbsent(i, i1 -> targetGraph.addVertex()); + } + + 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]); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/PlantedPartitionGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/PlantedPartitionGraphGenerator.java new file mode 100644 index 00000000000..10c4e7edea1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/PlantedPartitionGraphGenerator.java @@ -0,0 +1,306 @@ +/* + * (C) Copyright 2018-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/PruferTreeGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/PruferTreeGenerator.java new file mode 100644 index 00000000000..bb445cec11b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/PruferTreeGenerator.java @@ -0,0 +1,222 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/RandomGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/RandomGraphGenerator.java deleted file mode 100644 index b55bf5ac506..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/RandomGraphGenerator.java +++ /dev/null @@ -1,381 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * RandomGraphGenerator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.generate; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * This Generator creates a random-topology graph of a specified number of - * vertexes and edges. An instance of this generator will always return the same - * graph-topology in calls to generateGraph(). The vertexes can be different - * (depends on the VertexFactory implementation) - * - *

    However, two instances which use the same constructor parameters will - * produce two different random graphs (note: as with any random generator, - * there is always a small possibility that two instances will create the same - * results). - * - * @author Assaf Lehr - * @since Aug 6, 2005 - */ -public class RandomGraphGenerator - implements GraphGenerator -{ - //~ Static fields/initializers --------------------------------------------- - - private static long seedUniquifier = 8682522807148012L; - - //~ Instance fields -------------------------------------------------------- - - protected int numOfVertexes; - protected int numOfEdges; - protected Random randomizer; - private long randomizerSeed; - - //~ Constructors ----------------------------------------------------------- - - public RandomGraphGenerator(int aNumOfVertexes, int aNumOfEdges) - { - if ((aNumOfVertexes < 0) || (aNumOfEdges < 0)) { - throw new IllegalArgumentException("must be non-negative"); - } - this.numOfVertexes = aNumOfVertexes; - this.numOfEdges = aNumOfEdges; - - this.randomizerSeed = chooseRandomSeedOnce(); - this.randomizer = new Random(this.randomizerSeed); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Should be called only once on creation. Chooses a seed which can be used - * later to reset the randomizer before each method call. This - * implementation copies the java.util.Random constructor because there is - * no getSeed() there, and seed is protected. - * - * @author Assaf - * @since Aug 6, 2005 - */ - private synchronized static long chooseRandomSeedOnce() - { - return (++seedUniquifier + System.nanoTime()); - } - - /** - * Resets seed to generate the same random stream. - */ - private void resetRandomSeed() - { - this.randomizer.setSeed(this.randomizerSeed); - } - - /** - * (non-Javadoc) - * - * @throws IllegalArgumentException if the aNumOfEdges passed in the - * constructor, cannot be created on a graph of the concrete type with - * aNumOfVertexes. - * org.jgrapht.generate.RandomGraphGenerator.DefaultEdgeTopologyFactory#isNumberOfEdgesValid(org.jgrapht.Graph, - * int) - * - * @see GraphGenerator#generateGraph(Graph, VertexFactory, Map) - */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) - { - resetRandomSeed(); - - // key = generation order (1st,2nd,3rd,...) value=vertex Object - // will be used later - Map orderToVertexMap = - new HashMap(this.numOfVertexes); - - for (int i = 0; i < this.numOfVertexes; i++) { - V currVertex = vertexFactory.createVertex(); - target.addVertex(currVertex); - orderToVertexMap.put(Integer.valueOf(i), currVertex); - } - - if (target.vertexSet().size() != numOfVertexes) { - throw new IllegalArgumentException( - "Vertex factory did not produce " + numOfVertexes - + " distinct vertices."); - } - - // use specific type of edge factory, depending of the graph type - // and edge density - EdgeTopologyFactory edgesFactory = - edgeTopologyFactoryChooser(target, numOfEdges); - if (!edgesFactory.isNumberOfEdgesValid(target, numOfEdges)) { - throw new IllegalArgumentException( - "numOfEdges is not valid for the graph type " - + "\n-> Invalid number Of Edges=" + numOfEdges + " for:" - + " graph type=" + target.getClass() - + " ,number Of Vertexes=" + this.numOfVertexes - + "\n-> Advice: For the Max value , check the javadoc for" - + " org.jgrapht.generate.RandomGraphGenerator.DefaultEdgeTopologyFactory"); - } - - edgesFactory.createEdges( - target, - orderToVertexMap, - this.numOfEdges, - this.randomizer); - } - - /** - * Returns a concrete EdgeTopologyFactory, depending on graph type and - * numOfEdges - * - * @param target - * - * @return - */ - private EdgeTopologyFactory edgeTopologyFactoryChooser( - Graph target, - int numOfEdges) - { - return new DefaultEdgeTopologyFactory(); - } - - //~ Inner Interfaces ------------------------------------------------------- - - /** - * This class is used to generate the edge topology for a graph. - * - * @author Assaf - * @since Aug 6, 2005 - */ - public interface EdgeTopologyFactory - { - /** - * Two different calls to the createEdges() with the same parameters - * must result in the generation of the same. But if the randomizer is - * different, it should, usually, create different edge topology. - * - * @param targetGraph - guranteed to start with zero edges. - * @param orderToVertexMap - key=Integer of vertex order . between zero - * to numOfVertexes (exclusive). value = vertex from the graph. unique. - * @param numberOfEdges - to create in the graph - * @param randomizer - */ - public void createEdges( - Graph targetGraph, - Map orderToVertexMap, - int numberOfEdges, - Random randomizer); - - /** - * Checks if the graph can contain the givven numberOfEdges according to - * the graph type restrictions. For example: #V means number of - * vertexes in graph - *

  • a Simple Graph, can have max of #V*(#V-1)/2 edges. etc - * - * @param targetGraph guranteed to start with zero edges. - * @param numberOfEdges - */ - public boolean isNumberOfEdgesValid( - Graph targetGraph, - int numberOfEdges); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * Default implementation of the EdgeTopologyFactory interface. randomly - * chooses an edge and tries to add it. If the add fails from any reason - * (like: self edge / multiple edges in unpermitted graph type) it will just - * choose another and try again. Performance: - *
  • when the number of possible edges becomes slim , this class will have - * a very poor performance , cause it will not use gready methods to choose - * them. for example : In simple graph , if #V = N (#x = number Of x) and we - * want full mesh #edges= N*(N-1)/2 , the first added edges will do so - * quickly (O(1) , the last will take O(N^2). So , do not use it in this - * kind of graphs. - *
  • If the numberOfEdges is bigger than what the graph can add, there - * will be an infinite loop here. It is not tested. - * - * @author Assaf - * @since Aug 6, 2005 - */ - public class DefaultEdgeTopologyFactory - implements EdgeTopologyFactory - { - public void createEdges( - Graph targetGraph, - Map orderToVertexMap, - int numberOfEdges, - Random randomizer) - { - int iterationsCounter = 0; - int edgesCounter = 0; - while (edgesCounter < numberOfEdges) { - // randomizer.nextInt(int n) return a number between zero - // (inclusive) and n(exclusive) - VV startVertex = - orderToVertexMap.get( - Integer.valueOf(randomizer.nextInt(numOfVertexes))); - VV endVertex = - orderToVertexMap.get( - Integer.valueOf(randomizer.nextInt(numOfVertexes))); - try { - EE resultEdge = targetGraph.addEdge(startVertex, endVertex); - if (resultEdge != null) { - edgesCounter++; - } - } catch (Exception e) { - // do nothing.just ignore the edge - } - - iterationsCounter++; - } - } - - /** - * checks if the numOfEdges is smaller than the Max edges according to - * the following table: - * - *

    - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    Graph TypeDirected / UnDirectedmultiple edgesloopsMax Edges
    SimpleGraphUnDirected--N(N-1)/2
    MultigraphUnDirected+-Infinite
    PseudographUnDirected++Infinite
    SimpleDirectedGraphDirected--N (N-1)
    DefaultDirectedGraphDirected-+N*(N-1)+ N = N^2
    DirectedMultigraphDirected++Infinite
    - * - * @see RandomGraphGenerator.EdgeTopologyFactory#isNumberOfEdgesValid(Graph, - * int) - */ - public boolean isNumberOfEdgesValid( - Graph targetGraph, - int numberOfEdges) - { - boolean result; - - boolean infinite = false; - int maxAllowedEdges = getMaxEdgesForVertexNum(targetGraph); - if (maxAllowedEdges == -1) { - infinite = true; - } - - if (true == infinite) { - result = true; - } else if (numberOfEdges <= maxAllowedEdges) { - result = true; - } else { - result = false; - } - return result; - } - - /** - * Return max edges for that graph. If it is infinite return -1 instead. - */ - public int getMaxEdgesForVertexNum(Graph targetGraph) - { - int maxAllowedEdges = 0; - if (targetGraph instanceof SimpleGraph) { - maxAllowedEdges = numOfVertexes * (numOfVertexes - 1) / 2; - } else if (targetGraph instanceof SimpleDirectedGraph) { - maxAllowedEdges = numOfVertexes * (numOfVertexes - 1); - } else if (targetGraph instanceof DefaultDirectedGraph) { - maxAllowedEdges = numOfVertexes * numOfVertexes; - } else { - // This may be overly liberal in the case of something - // like a simple graph which has been wrapped with - // a graph adapter or view. - maxAllowedEdges = -1; // infinite - } - return maxAllowedEdges; - } - } -} - -// End RandomGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/RandomRegularGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/RandomRegularGraphGenerator.java new file mode 100644 index 00000000000..8fbb22302fe --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/RandomRegularGraphGenerator.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/RingGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/RingGraphGenerator.java index 927a1b33407..dce2c9a1372 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/RingGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/RingGraphGenerator.java @@ -1,66 +1,40 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * RingGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. + * 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 - * @since Sep 16, 2003 */ public class RingGraphGenerator implements GraphGenerator { - //~ Instance fields -------------------------------------------------------- - - private int size; - - //~ Constructors ----------------------------------------------------------- + private final int size; /** * Construct a new RingGraphGenerator. @@ -74,33 +48,24 @@ public RingGraphGenerator(int size) if (size < 0) { throw new IllegalArgumentException("must be non-negative"); } - this.size = size; } - //~ Methods ---------------------------------------------------------------- - /** * {@inheritDoc} */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { if (size < 1) { return; } - LinearGraphGenerator linearGenerator = - new LinearGraphGenerator(size); - Map privateMap = new HashMap(); - linearGenerator.generateGraph(target, vertexFactory, privateMap); + 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); } } - -// End RingGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/ScaleFreeGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/ScaleFreeGraphGenerator.java index 18a1bed0b47..d3a0110cdce 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/ScaleFreeGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/ScaleFreeGraphGenerator.java @@ -1,143 +1,105 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2008-2023, by Ilya Razenshteyn and Contributors. * - * 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. - */ -/* ----------------- - * ScaleFreeGraphGenerator.java - * ----------------- - * (C) Copyright 2008-2008, by Ilya Razenshteyn and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Ilya Razenshteyn - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; -import java.util.*; - 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. + * 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 { - //~ Instance fields -------------------------------------------------------- - - private int size; // size of graphs, generated by this instance of generator - private long seed; // initial seed - private Random random; // the source of randomness - - //~ Constructors ----------------------------------------------------------- + private final int size; + private final Random rng; /** - * Constructs a new ScaleFreeGraphGenerator. + * Constructor * * @param size number of vertices to be generated */ - public ScaleFreeGraphGenerator( - int size) + public ScaleFreeGraphGenerator(int size) { - if (size < 0) { - throw new IllegalArgumentException( - "invalid size: " + size + " (must be non-negative)"); - } - this.size = size; - random = new Random(); - seed = random.nextLong(); + this(size, new Random()); } /** - * Constructs a new ScaleFreeGraphGenerator using fixed - * seed for the random generator. + * Constructor * * @param size number of vertices to be generated * @param seed initial seed for the random generator */ - public ScaleFreeGraphGenerator( - int size, - long seed) + 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)"); + throw new IllegalArgumentException("invalid size: " + size + " (must be non-negative)"); } this.size = size; - random = new Random(); - this.seed = seed; + this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } - //~ Methods ---------------------------------------------------------------- - /** - * Generates scale-free network with size passed to the - * constructor. Each call of this method produces identical output (but if - * target is an undirected graph, the directions of edges will be - * lost). + * Generates scale-free network with {@code 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 vertexFactory called to produce new vertices - * @param resultMap unused parameter + * @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 */ - public void generateGraph( - Graph target, - VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { - random.setSeed(seed); - List vertexList = new ArrayList(); - List degrees = new ArrayList(); + List vertexList = new ArrayList<>(); + List degrees = new ArrayList<>(); int degreeSum = 0; for (int i = 0; i < size; i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); + V newVertex = target.addVertex(); int newDegree = 0; - while ((newDegree == 0) && (i != 0)) // we want our graph to be - // connected - + while ((newDegree == 0) && (i != 0)) // we want our graph to be connected { for (int j = 0; j < vertexList.size(); j++) { - if ((degreeSum == 0) - || (random.nextInt(degreeSum) < degrees.get(j))) - { + if ((degreeSum == 0) || (rng.nextInt(degreeSum) < degrees.get(j))) { degrees.set(j, degrees.get(j) + 1); newDegree++; degreeSum += 2; - if (random.nextInt(2) == 0) { + if (rng.nextBoolean()) { target.addEdge(vertexList.get(j), newVertex); } else { target.addEdge(newVertex, vertexList.get(j)); @@ -150,5 +112,3 @@ public void generateGraph( } } } - -// End ScaleFreeGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedBipartiteGraphMatrixGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedBipartiteGraphMatrixGenerator.java new file mode 100644 index 00000000000..c7dde433b2e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedBipartiteGraphMatrixGenerator.java @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2016-2023, 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]); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedGraphMatrixGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedGraphMatrixGenerator.java new file mode 100644 index 00000000000..9c331866538 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/SimpleWeightedGraphMatrixGenerator.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2016-2023, 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]); + } + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/StarGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/StarGraphGenerator.java index b6ed2ad9b55..f64f6afdc96 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/StarGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/StarGraphGenerator.java @@ -1,116 +1,75 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2008-2023, by Andrew Newell and Contributors. * - * 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. - */ -/* ------------------- - * StarGraphGenerator.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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. + * 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 - * @since Dec 21, 2008 */ public class StarGraphGenerator implements GraphGenerator { - //~ Static fields/initializers --------------------------------------------- - public static final String CENTER_VERTEX = "Center Vertex"; - //~ Instance fields -------------------------------------------------------- - - private int order; - - //~ Constructors ----------------------------------------------------------- + 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; } - //~ Methods ---------------------------------------------------------------- - /** * Generates a star graph with the designated order from the constructor */ - public void generateGraph( - Graph target, - final VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { if (order < 1) { return; } - //Create center vertex - V centerVertex = vertexFactory.createVertex(); - target.addVertex(centerVertex); + // Create center vertex + V centerVertex = target.addVertex(); if (resultMap != null) { resultMap.put(CENTER_VERTEX, centerVertex); } - //Create other vertices + // Create other vertices for (int i = 0; i < (order - 1); i++) { - V newVertex = vertexFactory.createVertex(); - target.addVertex(newVertex); - } - - //Add one edge between the center vertex and every other vertex - Iterator iter = target.vertexSet().iterator(); - while (iter.hasNext()) { - V v = iter.next(); - if (v != centerVertex) { - target.addEdge(v, centerVertex); - } + target.addEdge(target.addVertex(), centerVertex); } } } - -// End StarGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/TooManyFailuresException.java b/jgrapht-core/src/main/java/org/jgrapht/generate/TooManyFailuresException.java new file mode 100644 index 00000000000..68e618f5b54 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/TooManyFailuresException.java @@ -0,0 +1,69 @@ +/* + * (C) Copyright 2019-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/WattsStrogatzGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/WattsStrogatzGraphGenerator.java new file mode 100644 index 00000000000..685860b2f8f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/WattsStrogatzGraphGenerator.java @@ -0,0 +1,195 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/WheelGraphGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/WheelGraphGenerator.java index 25823331665..8e3a1748f5c 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/WheelGraphGenerator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/WheelGraphGenerator.java @@ -1,80 +1,54 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * WheelGraphGenerator.java - * ------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sep-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - 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". + * 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 - * @since Sep 16, 2003 */ public class WheelGraphGenerator implements GraphGenerator { - //~ Static fields/initializers --------------------------------------------- - /** * Role for the hub vertex. */ public static final String HUB_VERTEX = "Hub Vertex"; - //~ Instance fields -------------------------------------------------------- - private boolean inwardSpokes; private int size; - //~ Constructors ----------------------------------------------------------- - /** - * 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. + * 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. */ @@ -87,10 +61,10 @@ public WheelGraphGenerator(int size) * 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. + * @param inwardSpokes if {@code true} and graph is directed, spokes are oriented from rim + * to hub; else from hub to rim. * - * @throws IllegalArgumentException + * @throws IllegalArgumentException in case the number of vertices is negative */ public WheelGraphGenerator(int size, boolean inwardSpokes) { @@ -102,41 +76,34 @@ public WheelGraphGenerator(int size, boolean inwardSpokes) this.inwardSpokes = inwardSpokes; } - //~ Methods ---------------------------------------------------------------- - /** * {@inheritDoc} */ - public void generateGraph( - Graph target, - final VertexFactory vertexFactory, - Map resultMap) + @Override + public void generateGraph(Graph target, Map resultMap) { if (size < 1) { return; } - // A little trickery to intercept the rim generation. This is + // 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(); - VertexFactory rimVertexFactory = - new VertexFactory() { - public V createVertex() - { - V vertex = vertexFactory.createVertex(); - rim.add(vertex); + final Collection rim = new ArrayList<>(); + final Supplier initialSupplier = target.getVertexSupplier(); + Supplier rimVertexSupplier = () -> { + V vertex = initialSupplier.get(); + rim.add(vertex); + return vertex; + }; - return vertex; - } - }; + Graph targetWithRimVertexSupplier = + new GraphDelegator<>(target, rimVertexSupplier, null); - RingGraphGenerator ringGenerator = - new RingGraphGenerator(size - 1); - ringGenerator.generateGraph(target, rimVertexFactory, resultMap); + new RingGraphGenerator(size - 1) + .generateGraph(targetWithRimVertexSupplier, resultMap); - V hubVertex = vertexFactory.createVertex(); - target.addVertex(hubVertex); + V hubVertex = target.addVertex(); if (resultMap != null) { resultMap.put(HUB_VERTEX, hubVertex); @@ -151,5 +118,3 @@ public V createVertex() } } } - -// End WheelGraphGenerator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/WindmillGraphsGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/WindmillGraphsGenerator.java new file mode 100644 index 00000000000..a6812e73502 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/WindmillGraphsGenerator.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2017-2023, 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)); + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/BipartiteMatchingProblem.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/BipartiteMatchingProblem.java new file mode 100644 index 00000000000..ed631b4089f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/BipartiteMatchingProblem.java @@ -0,0 +1,180 @@ +/* + * (C) Copyright 2020-2023, 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; + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/Distributor.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/Distributor.java new file mode 100644 index 00000000000..7f35048662c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/Distributor.java @@ -0,0 +1,217 @@ +/* + * (C) Copyright 2020-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/MaximumFlowProblem.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/MaximumFlowProblem.java new file mode 100644 index 00000000000..e85ee3b0753 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/MaximumFlowProblem.java @@ -0,0 +1,262 @@ +/* + * (C) Copyright 2020-2023, 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGenerator.java new file mode 100644 index 00000000000..6cba785aeab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGenerator.java @@ -0,0 +1,1038 @@ +/* + * (C) Copyright 2020-2023, 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); + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfig.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfig.java new file mode 100644 index 00000000000..7090640757b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfig.java @@ -0,0 +1,603 @@ +/* + * (C) Copyright 2020-2023, 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;
    • + *
    • 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilder.java new file mode 100644 index 00000000000..f96ece1bc27 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilder.java @@ -0,0 +1,497 @@ +/* + * (C) Copyright 2020-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkInfo.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkInfo.java new file mode 100644 index 00000000000..dd89e97ff83 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkInfo.java @@ -0,0 +1,161 @@ +/* + * (C) Copyright 2020-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/package-info.java new file mode 100644 index 00000000000..6ed77a328b0 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, 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 + */ + +/** + * Network generator components + */ +package org.jgrapht.generate.netgen; diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/generate/package-info.java new file mode 100644 index 00000000000..a5e97e354d4 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/generate/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Generators for graphs of various topologies. + */ +package org.jgrapht.generate; diff --git a/jgrapht-core/src/main/java/org/jgrapht/generate/package.html b/jgrapht-core/src/main/java/org/jgrapht/generate/package.html deleted file mode 100644 index cde8047ec03..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/generate/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Generators for graphs of various topologies. - - \ No newline at end of file diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractBaseGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractBaseGraph.java index 277be87a3ac..c2bbfbf168f 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractBaseGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractBaseGraph.java @@ -1,279 +1,336 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ---------------------- - * AbstractBaseGraph.java - * ---------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): John V. Sichi - * Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 10-Aug-2003 : General edge refactoring (BN); - * 06-Nov-2003 : Change edge sharing semantics (JVS); - * 07-Feb-2004 : Enabled serialization (BN); - * 11-Mar-2004 : Made generic (CH); - * 01-Jun-2005 : Added EdgeListFactory (JVS); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 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). + * + *

    + * 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. * - *

    This graph implementation guarantees deterministic vertex and edge set - * ordering (via {@link LinkedHashMap} and {@link LinkedHashSet}).

    + *

    + * 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 - * @since Jul 24, 2003 + * @author Dimitrios Michail */ public abstract class AbstractBaseGraph extends AbstractGraph - implements Graph, - Cloneable, - Serializable + implements Graph, Cloneable, Serializable { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = -1263088497616142427L; + 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"; - //~ Instance fields -------------------------------------------------------- + private transient Set unmodifiableVertexSet = null; - boolean allowingLoops; + private Supplier vertexSupplier; + private Supplier edgeSupplier; + private GraphType type; - private EdgeFactory edgeFactory; - private EdgeSetFactory edgeSetFactory; - private Map edgeMap; - private transient Set unmodifiableEdgeSet = null; - private transient Set unmodifiableVertexSet = null; - private Specifics specifics; - private boolean allowingMultipleEdges; + private Specifics specifics; + private IntrusiveEdgesSpecifics intrusiveEdgesSpecifics; + private GraphSpecificsStrategy graphSpecificsStrategy; - private transient TypeUtil vertexTypeDecl = null; + private transient GraphIterables graphIterables = null; - //~ Constructors ----------------------------------------------------------- + /** + * Construct a new graph. + * + * @param vertexSupplier the vertex supplier, can be {@code null} + * @param edgeSupplier the edge supplier, can be {@code 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 pseudograph. The pseudograph can either be directed or - * undirected, depending on the specified edge factory. + * Construct a new graph. * - * @param ef the edge factory of the new graph. - * @param allowMultipleEdges whether to allow multiple edges or not. - * @param allowLoops whether to allow edges that are self-loops or not. + * @param vertexSupplier the vertex supplier, can be {@code null} + * @param edgeSupplier the edge supplier, can be {@code null} + * @param type the graph type + * @param graphSpecificsStrategy strategy for constructing low-level graph specifics * - * @throws NullPointerException if the specified edge factory is - * null. + * @throws IllegalArgumentException if the graph type is mixed + * @throws NullPointerException if either one of {@code type} or {@code graphSpecificsStragegy} + * is {@code null}, or if {@code graphSpecificsStragegy} generates + * {@code null} */ - public AbstractBaseGraph( - EdgeFactory ef, - boolean allowMultipleEdges, - boolean allowLoops) + protected AbstractBaseGraph( + Supplier vertexSupplier, Supplier edgeSupplier, GraphType type, + GraphSpecificsStrategy graphSpecificsStrategy) { - if (ef == null) { - throw new NullPointerException(); + this.vertexSupplier = vertexSupplier; + this.edgeSupplier = edgeSupplier; + this.type = Objects.requireNonNull(type); + if (type.isMixed()) { + throw new IllegalArgumentException(MIXED_GRAPH_NOT_SUPPORTED); } - edgeMap = new LinkedHashMap(); - edgeFactory = ef; - allowingLoops = allowLoops; - allowingMultipleEdges = allowMultipleEdges; - - specifics = createSpecifics(); + 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); - this.edgeSetFactory = new ArrayListFactory(); } - //~ Methods ---------------------------------------------------------------- - /** - * @see Graph#getAllEdges(Object, Object) + * {@inheritDoc} */ + @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return specifics.getAllEdges(sourceVertex, targetVertex); } - /** - * 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 loops are allowed. - */ - public boolean isAllowingLoops() + @Override + public Supplier getEdgeSupplier() { - return allowingLoops; + return edgeSupplier; } /** - * Returns true if and only if multiple 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 edges are allowed. + * 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 {@code 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 {@code e2} such that {@code e2.equals(e)}. + * + * @param edgeSupplier the edge supplier */ - public boolean isAllowingMultipleEdges() + public void setEdgeSupplier(Supplier edgeSupplier) { - return allowingMultipleEdges; + this.edgeSupplier = edgeSupplier; } - /** - * @see Graph#getEdge(Object, Object) - */ - public E getEdge(V sourceVertex, V targetVertex) + @Override + public Supplier getVertexSupplier() { - return specifics.getEdge(sourceVertex, targetVertex); + return vertexSupplier; } /** - * @see Graph#getEdgeFactory() + * 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 {@code v} must not be equal + * to any other vertex in the graph. More formally, the graph must not contain any vertex + * {@code v2} such that {@code 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 EdgeFactory getEdgeFactory() + public void setVertexSupplier(Supplier vertexSupplier) { - return edgeFactory; + this.vertexSupplier = vertexSupplier; } /** - * Set the {@link EdgeSetFactory} to use for this graph. Initially, a graph - * is created with a default implementation which always supplies an {@link - * java.util.ArrayList} with capacity 1. - * - * @param edgeSetFactory factory to use for subsequently created edge sets - * (this call has no effect on existing edge sets) + * {@inheritDoc} */ - public void setEdgeSetFactory(EdgeSetFactory edgeSetFactory) + @Override + public E getEdge(V sourceVertex, V targetVertex) { - this.edgeSetFactory = edgeSetFactory; + return specifics.getEdge(sourceVertex, targetVertex); } /** - * @see Graph#addEdge(Object, Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public E addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); - if (!allowingMultipleEdges - && containsEdge(sourceVertex, targetVertex)) - { - return null; - } - - if (!allowingLoops && sourceVertex.equals(targetVertex)) { + if (!type.isAllowingSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } - E e = edgeFactory.createEdge(sourceVertex, targetVertex); - - if (containsEdge(e)) { // this restriction should stay! + if (edgeSupplier == null) { + throw new UnsupportedOperationException(THE_GRAPH_CONTAINS_NO_EDGE_SUPPLIER); + } - return null; + 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 { - IntrusiveEdge intrusiveEdge = - createIntrusiveEdge(e, sourceVertex, targetVertex); - - edgeMap.put(e, intrusiveEdge); - specifics.addEdgeToTouchingVertices(e); - - return e; + E e = edgeSupplier.get(); + if (intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex)) { + specifics.addEdgeToTouchingVertices(sourceVertex, targetVertex, e); + return e; + } } + return null; } /** - * @see Graph#addEdge(Object, Object, Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @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); - if (!allowingMultipleEdges - && containsEdge(sourceVertex, targetVertex)) - { - return false; - } - - if (!allowingLoops && sourceVertex.equals(targetVertex)) { + if (!type.isAllowingSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } - IntrusiveEdge intrusiveEdge = - createIntrusiveEdge(e, sourceVertex, targetVertex); + if (!type.isAllowingMultipleEdges()) { - edgeMap.put(e, intrusiveEdge); - specifics.addEdgeToTouchingVertices(e); - - return true; + 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; + } } - private IntrusiveEdge createIntrusiveEdge( - E e, - V sourceVertex, - V targetVertex) + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + */ + @Override + public V addVertex() { - IntrusiveEdge intrusiveEdge; - if (e instanceof IntrusiveEdge) { - intrusiveEdge = (IntrusiveEdge) e; - } else { - intrusiveEdge = new IntrusiveEdge(); + 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); } - intrusiveEdge.source = sourceVertex; - intrusiveEdge.target = targetVertex; - return intrusiveEdge; + return v; } /** - * @see Graph#addVertex(Object) + * @throws NullPointerException {@inheritDoc} */ + @Override public boolean addVertex(V v) { if (v == null) { @@ -282,178 +339,188 @@ public boolean addVertex(V v) return false; } else { specifics.addVertex(v); - return true; } } /** - * @see Graph#getEdgeSource(Object) + * {@inheritDoc} */ + @Override public V getEdgeSource(E e) { - return TypeUtil.uncheckedCast( - getIntrusiveEdge(e).source, - vertexTypeDecl); + return intrusiveEdgesSpecifics.getEdgeSource(e); } /** - * @see Graph#getEdgeTarget(Object) + * {@inheritDoc} */ + @Override public V getEdgeTarget(E e) { - return TypeUtil.uncheckedCast( - getIntrusiveEdge(e).target, - vertexTypeDecl); - } - - private IntrusiveEdge getIntrusiveEdge(E e) - { - if (e instanceof IntrusiveEdge) { - return (IntrusiveEdge) e; - } - - return edgeMap.get(e); + return intrusiveEdgesSpecifics.getEdgeTarget(e); } /** - * Returns a shallow copy of this graph instance. Neither edges nor vertices - * are cloned. + * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * - * @return a shallow copy of this set. + * @return a shallow copy of this graph. * - * @throws RuntimeException + * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ + @Override public Object clone() { try { - TypeUtil> typeDecl = null; - - AbstractBaseGraph newGraph = - TypeUtil.uncheckedCast(super.clone(), typeDecl); + AbstractBaseGraph newGraph = TypeUtil.uncheckedCast(super.clone()); - newGraph.edgeMap = new LinkedHashMap(); - - newGraph.edgeFactory = this.edgeFactory; - newGraph.unmodifiableEdgeSet = null; + newGraph.vertexSupplier = this.vertexSupplier; + newGraph.edgeSupplier = this.edgeSupplier; + newGraph.type = type; newGraph.unmodifiableVertexSet = null; - // NOTE: it's important for this to happen in an object + 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.createSpecifics(); + 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(); + throw new RuntimeException(e); } } /** - * @see Graph#containsEdge(Object) + * {@inheritDoc} */ + @Override public boolean containsEdge(E e) { - return edgeMap.containsKey(e); + return intrusiveEdgesSpecifics.containsEdge(e); } /** - * @see Graph#containsVertex(Object) + * {@inheritDoc} */ + @Override public boolean containsVertex(V v) { return specifics.getVertexSet().contains(v); } /** - * @see UndirectedGraph#degreeOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public int degreeOf(V vertex) { + assertVertexExist(vertex); return specifics.degreeOf(vertex); } /** - * @see Graph#edgeSet() + * {@inheritDoc} */ + @Override public Set edgeSet() { - if (unmodifiableEdgeSet == null) { - unmodifiableEdgeSet = Collections.unmodifiableSet(edgeMap.keySet()); - } - - return unmodifiableEdgeSet; + return intrusiveEdgesSpecifics.getEdgeSet(); } /** - * @see Graph#edgesOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public Set edgesOf(V vertex) { + assertVertexExist(vertex); return specifics.edgesOf(vertex); } /** - * @see DirectedGraph#inDegreeOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public int inDegreeOf(V vertex) { + assertVertexExist(vertex); return specifics.inDegreeOf(vertex); } /** - * @see DirectedGraph#incomingEdgesOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public Set incomingEdgesOf(V vertex) { + assertVertexExist(vertex); return specifics.incomingEdgesOf(vertex); } /** - * @see DirectedGraph#outDegreeOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public int outDegreeOf(V vertex) { + assertVertexExist(vertex); return specifics.outDegreeOf(vertex); } /** - * @see DirectedGraph#outgoingEdgesOf(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ + @Override public Set outgoingEdgesOf(V vertex) { + assertVertexExist(vertex); return specifics.outgoingEdgesOf(vertex); } /** - * @see Graph#removeEdge(Object, Object) + * {@inheritDoc} */ + @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = getEdge(sourceVertex, targetVertex); if (e != null) { - specifics.removeEdgeFromTouchingVertices(e); - edgeMap.remove(e); + specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); + intrusiveEdgesSpecifics.remove(e); } return e; } /** - * @see Graph#removeEdge(Object) + * {@inheritDoc} */ + @Override public boolean removeEdge(E e) { if (containsEdge(e)) { - specifics.removeEdgeFromTouchingVertices(e); - edgeMap.remove(e); - + V sourceVertex = getEdgeSource(e); + V targetVertex = getEdgeTarget(e); + specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); + intrusiveEdgesSpecifics.remove(e); return true; } else { return false; @@ -461,8 +528,9 @@ public boolean removeEdge(E e) } /** - * @see Graph#removeVertex(Object) + * {@inheritDoc} */ + @Override public boolean removeVertex(V v) { if (containsVertex(v)) { @@ -470,7 +538,7 @@ public boolean removeVertex(V v) // cannot iterate over list - will cause // ConcurrentModificationException - removeAllEdges(new ArrayList(touchingEdgesList)); + removeAllEdges(new ArrayList<>(touchingEdgesList)); specifics.getVertexSet().remove(v); // remove the vertex itself @@ -481,742 +549,59 @@ public boolean removeVertex(V v) } /** - * @see Graph#vertexSet() + * {@inheritDoc} */ + @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { - unmodifiableVertexSet = - Collections.unmodifiableSet(specifics.getVertexSet()); + unmodifiableVertexSet = Collections.unmodifiableSet(specifics.getVertexSet()); } return unmodifiableVertexSet; } /** - * @see Graph#getEdgeWeight(Object) + * @throws NullPointerException {@inheritDoc} */ + @Override public double getEdgeWeight(E e) { - if (e instanceof DefaultWeightedEdge) { - return ((DefaultWeightedEdge) e).getWeight(); - } else { - return WeightedGraph.DEFAULT_EDGE_WEIGHT; + if (e == null) { + throw new NullPointerException(); } + return intrusiveEdgesSpecifics.getEdgeWeight(e); } /** - * @see WeightedGraph#setEdgeWeight(Object, double) + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public void setEdgeWeight(E e, double weight) { - assert (e instanceof DefaultWeightedEdge) : e.getClass(); - ((DefaultWeightedEdge) e).weight = weight; - } - - private Specifics createSpecifics() - { - if (this instanceof DirectedGraph) { - return new DirectedSpecifics(); - } else if (this instanceof UndirectedGraph) { - return new UndirectedSpecifics(); - } else { - throw new IllegalArgumentException( - "must be instance of either DirectedGraph or UndirectedGraph"); - } - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * . - * - * @author Barak Naveh - */ - private abstract class Specifics - implements Serializable - { - private static final long serialVersionUID = 785196247314761183L; - - public abstract void addVertex(V vertex); - - public abstract Set getVertexSet(); - - /** - * . - * - * @param sourceVertex - * @param targetVertex - * - * @return - */ - public abstract Set getAllEdges(V sourceVertex, - V targetVertex); - - /** - * . - * - * @param sourceVertex - * @param targetVertex - * - * @return - */ - public abstract E getEdge(V sourceVertex, V targetVertex); - - /** - * Adds the specified edge to the edge containers of its source and - * target vertices. - * - * @param e - */ - public abstract void addEdgeToTouchingVertices(E e); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract int degreeOf(V vertex); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract Set edgesOf(V vertex); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract int inDegreeOf(V vertex); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract Set incomingEdgesOf(V vertex); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract int outDegreeOf(V vertex); - - /** - * . - * - * @param vertex - * - * @return - */ - public abstract Set outgoingEdgesOf(V vertex); - - /** - * Removes the specified edge from the edge containers of its source and - * target vertices. - * - * @param e - */ - public abstract void removeEdgeFromTouchingVertices(E e); - } - - private static class ArrayListFactory - implements EdgeSetFactory, - Serializable - { - private static final long serialVersionUID = 5936902837403445985L; - - /** - * @see EdgeSetFactory.createEdgeSet - */ - public Set createEdgeSet(VV vertex) - { - // NOTE: use size 1 to keep memory usage under control - // for the common case of vertices with low degree - return new ArrayUnenforcedSet(1); - } - } - - /** - * 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).

    - * - * @author Barak Naveh - */ - private static 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, - VV vertex) - { - incoming = edgeSetFactory.createEdgeSet(vertex); - outgoing = edgeSetFactory.createEdgeSet(vertex); - } - - /** - * A lazy build of unmodifiable incoming edge set. - * - * @return - */ - public Set getUnmodifiableIncomingEdges() - { - if (unmodifiableIncoming == null) { - unmodifiableIncoming = Collections.unmodifiableSet(incoming); - } - - return unmodifiableIncoming; - } - - /** - * A lazy build of unmodifiable outgoing edge set. - * - * @return - */ - public Set getUnmodifiableOutgoingEdges() - { - if (unmodifiableOutgoing == null) { - unmodifiableOutgoing = Collections.unmodifiableSet(outgoing); - } - - return unmodifiableOutgoing; - } - - /** - * . - * - * @param e - */ - public void addIncomingEdge(EE e) - { - incoming.add(e); - } - - /** - * . - * - * @param e - */ - public void addOutgoingEdge(EE e) - { - outgoing.add(e); - } - - /** - * . - * - * @param e - */ - public void removeIncomingEdge(EE e) - { - incoming.remove(e); - } - - /** - * . - * - * @param e - */ - public void removeOutgoingEdge(EE e) - { - outgoing.remove(e); - } - } - - /** - * . - * - * @author Barak Naveh - */ - private class DirectedSpecifics - extends Specifics - implements Serializable - { - private static final long serialVersionUID = 8971725103718958232L; - private static final String NOT_IN_DIRECTED_GRAPH = - "no such operation in a directed graph"; - - private Map> vertexMapDirected = - new LinkedHashMap>(); - - public void addVertex(V v) - { - // add with a lazy edge container entry - vertexMapDirected.put(v, null); - } - - public Set getVertexSet() - { - return vertexMapDirected.keySet(); - } - - /** - * @see Graph#getAllEdges(Object, Object) - */ - public Set getAllEdges(V sourceVertex, V targetVertex) - { - Set edges = null; - - if (containsVertex(sourceVertex) - && containsVertex(targetVertex)) - { - edges = new ArrayUnenforcedSet(); - - DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); - - Iterator iter = ec.outgoing.iterator(); - - while (iter.hasNext()) { - E e = iter.next(); - - if (getEdgeTarget(e).equals(targetVertex)) { - edges.add(e); - } - } - } - - return edges; - } - - /** - * @see Graph#getEdge(Object, Object) - */ - public E getEdge(V sourceVertex, V targetVertex) - { - if (containsVertex(sourceVertex) - && containsVertex(targetVertex)) - { - DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); - - Iterator iter = ec.outgoing.iterator(); - - while (iter.hasNext()) { - E e = iter.next(); - - if (getEdgeTarget(e).equals(targetVertex)) { - return e; - } - } - } - - return null; - } - - /** - * @see AbstractBaseGraph#addEdgeToTouchingVertices(Edge) - */ - public void addEdgeToTouchingVertices(E e) - { - V source = getEdgeSource(e); - V target = getEdgeTarget(e); - - getEdgeContainer(source).addOutgoingEdge(e); - getEdgeContainer(target).addIncomingEdge(e); - } - - /** - * @see UndirectedGraph#degreeOf(Object) - */ - public int degreeOf(V vertex) - { - throw new UnsupportedOperationException(NOT_IN_DIRECTED_GRAPH); - } - - /** - * @see Graph#edgesOf(Object) - */ - public Set edgesOf(V vertex) - { - ArrayUnenforcedSet inAndOut = - new ArrayUnenforcedSet(getEdgeContainer(vertex).incoming); - inAndOut.addAll(getEdgeContainer(vertex).outgoing); - - // we have two copies for each self-loop - remove one of them. - if (allowingLoops) { - Set loops = getAllEdges(vertex, vertex); - - for (int i = 0; i < inAndOut.size();) { - Object e = inAndOut.get(i); - - if (loops.contains(e)) { - inAndOut.remove(i); - loops.remove(e); // so we remove it only once - } else { - i++; - } - } - } - - return Collections.unmodifiableSet(inAndOut); - } - - /** - * @see DirectedGraph#inDegree(Object) - */ - public int inDegreeOf(V vertex) - { - return getEdgeContainer(vertex).incoming.size(); - } - - /** - * @see DirectedGraph#incomingEdges(Object) - */ - public Set incomingEdgesOf(V vertex) - { - return getEdgeContainer(vertex).getUnmodifiableIncomingEdges(); - } - - /** - * @see DirectedGraph#outDegree(Object) - */ - public int outDegreeOf(V vertex) - { - return getEdgeContainer(vertex).outgoing.size(); - } - - /** - * @see DirectedGraph#outgoingEdges(Object) - */ - public Set outgoingEdgesOf(V vertex) - { - return getEdgeContainer(vertex).getUnmodifiableOutgoingEdges(); - } - - /** - * @see AbstractBaseGraph#removeEdgeFromTouchingVertices(Edge) - */ - public void removeEdgeFromTouchingVertices(E e) - { - V source = getEdgeSource(e); - V target = getEdgeTarget(e); - - getEdgeContainer(source).removeOutgoingEdge(e); - getEdgeContainer(target).removeIncomingEdge(e); - } - - /** - * A lazy build of edge container for specified vertex. - * - * @param vertex a vertex in this graph. - * - * @return EdgeContainer - */ - private DirectedEdgeContainer getEdgeContainer(V vertex) - { - assertVertexExist(vertex); - - DirectedEdgeContainer ec = vertexMapDirected.get(vertex); - - if (ec == null) { - ec = new DirectedEdgeContainer(edgeSetFactory, vertex); - vertexMapDirected.put(vertex, ec); - } - - return ec; + if (e == null) { + throw new NullPointerException(); } + intrusiveEdgesSpecifics.setEdgeWeight(e, weight); } /** - * A container of 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).

    - * - * @author Barak Naveh + * {@inheritDoc} */ - private static class UndirectedEdgeContainer - implements Serializable + @Override + public GraphType getType() { - private static final long serialVersionUID = -6623207588411170010L; - Set vertexEdges; - private transient Set unmodifiableVertexEdges = null; - - UndirectedEdgeContainer( - EdgeSetFactory edgeSetFactory, - VV vertex) - { - vertexEdges = edgeSetFactory.createEdgeSet(vertex); - } - - /** - * A lazy build of unmodifiable list of vertex edges - * - * @return - */ - public Set getUnmodifiableVertexEdges() - { - if (unmodifiableVertexEdges == null) { - unmodifiableVertexEdges = - Collections.unmodifiableSet(vertexEdges); - } - - return unmodifiableVertexEdges; - } - - /** - * . - * - * @param e - */ - public void addEdge(EE e) - { - vertexEdges.add(e); - } - - /** - * . - * - * @return - */ - public int edgeCount() - { - return vertexEdges.size(); - } - - /** - * . - * - * @param e - */ - public void removeEdge(EE e) - { - vertexEdges.remove(e); - } + return type; } - /** - * . - * - * @author Barak Naveh - */ - private class UndirectedSpecifics - extends Specifics - implements Serializable + @Override + public GraphIterables iterables() { - private static final long serialVersionUID = 6494588405178655873L; - private static final String NOT_IN_UNDIRECTED_GRAPH = - "no such operation in an undirected graph"; - - private Map> vertexMapUndirected = - new LinkedHashMap>(); - - public void addVertex(V v) - { - // add with a lazy edge container entry - vertexMapUndirected.put(v, null); - } - - public Set getVertexSet() - { - return vertexMapUndirected.keySet(); - } - - /** - * @see Graph#getAllEdges(Object, Object) - */ - public Set getAllEdges(V sourceVertex, V targetVertex) - { - Set edges = null; - - if (containsVertex(sourceVertex) - && containsVertex(targetVertex)) - { - edges = new ArrayUnenforcedSet(); - - Iterator iter = - getEdgeContainer(sourceVertex).vertexEdges.iterator(); - - while (iter.hasNext()) { - E e = iter.next(); - - boolean equalStraight = - sourceVertex.equals(getEdgeSource(e)) - && targetVertex.equals(getEdgeTarget(e)); - - boolean equalInverted = - sourceVertex.equals(getEdgeTarget(e)) - && targetVertex.equals(getEdgeSource(e)); - - if (equalStraight || equalInverted) { - edges.add(e); - } - } - } - - return edges; - } - - /** - * @see Graph#getEdge(Object, Object) - */ - public E getEdge(V sourceVertex, V targetVertex) - { - if (containsVertex(sourceVertex) - && containsVertex(targetVertex)) - { - Iterator iter = - getEdgeContainer(sourceVertex).vertexEdges.iterator(); - - while (iter.hasNext()) { - E e = iter.next(); - - boolean equalStraight = - sourceVertex.equals(getEdgeSource(e)) - && targetVertex.equals(getEdgeTarget(e)); - - boolean equalInverted = - sourceVertex.equals(getEdgeTarget(e)) - && targetVertex.equals(getEdgeSource(e)); - - if (equalStraight || equalInverted) { - return e; - } - } - } - - return null; - } - - /** - * @see AbstractBaseGraph#addEdgeToTouchingVertices(Edge) - */ - public void addEdgeToTouchingVertices(E e) - { - V source = getEdgeSource(e); - V target = getEdgeTarget(e); - - getEdgeContainer(source).addEdge(e); - - if (!source.equals(target)) { - getEdgeContainer(target).addEdge(e); - } - } - - /** - * @see UndirectedGraph#degreeOf(V) - */ - public int degreeOf(V vertex) - { - if (allowingLoops) { // then we must count, and add loops twice - - int degree = 0; - Set edges = getEdgeContainer(vertex).vertexEdges; - - for (E e : edges) { - if (getEdgeSource(e).equals(getEdgeTarget(e))) { - degree += 2; - } else { - degree += 1; - } - } - - return degree; - } else { - return getEdgeContainer(vertex).edgeCount(); - } - } - - /** - * @see Graph#edgesOf(V) - */ - public Set edgesOf(V vertex) - { - return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); - } - - /** - * @see DirectedGraph#inDegreeOf(Object) - */ - public int inDegreeOf(V vertex) - { - throw new UnsupportedOperationException(NOT_IN_UNDIRECTED_GRAPH); - } - - /** - * @see DirectedGraph#incomingEdgesOf(Object) - */ - public Set incomingEdgesOf(V vertex) - { - throw new UnsupportedOperationException(NOT_IN_UNDIRECTED_GRAPH); - } - - /** - * @see DirectedGraph#outDegreeOf(Object) - */ - public int outDegreeOf(V vertex) - { - throw new UnsupportedOperationException(NOT_IN_UNDIRECTED_GRAPH); - } - - /** - * @see DirectedGraph#outgoingEdgesOf(Object) - */ - public Set outgoingEdgesOf(V vertex) - { - throw new UnsupportedOperationException(NOT_IN_UNDIRECTED_GRAPH); - } - - /** - * @see AbstractBaseGraph#removeEdgeFromTouchingVertices(Edge) - */ - public void removeEdgeFromTouchingVertices(E e) - { - V source = getEdgeSource(e); - V target = getEdgeTarget(e); - - getEdgeContainer(source).removeEdge(e); - - if (!source.equals(target)) { - getEdgeContainer(target).removeEdge(e); - } - } - - /** - * A lazy build of edge container for specified vertex. - * - * @param vertex a vertex in this graph. - * - * @return EdgeContainer - */ - private UndirectedEdgeContainer getEdgeContainer(V vertex) - { - assertVertexExist(vertex); - - UndirectedEdgeContainer ec = vertexMapUndirected.get(vertex); - - if (ec == null) { - ec = new UndirectedEdgeContainer( - edgeSetFactory, - vertex); - vertexMapUndirected.put(vertex, ec); - } - - return ec; + // override interface to avoid instantiating frequently + if (graphIterables == null) { + graphIterables = new DefaultGraphIterables<>(this); } + return graphIterables; } } - -// End AbstractBaseGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractGraph.java index a059ee68486..582f09db300 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractGraph.java @@ -1,89 +1,65 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ------------------ - * AbstractGraph.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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. + * A skeletal implementation of the {@code 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 - * @see DirectedGraph - * @see UndirectedGraph */ public abstract class AbstractGraph implements Graph { - //~ Constructors ----------------------------------------------------------- - /** * Construct a new empty graph object. */ - public AbstractGraph() + protected AbstractGraph() { } - //~ Methods ---------------------------------------------------------------- - /** * @see Graph#containsEdge(Object, Object) */ + @Override public boolean containsEdge(V sourceVertex, V targetVertex) { return getEdge(sourceVertex, targetVertex) != null; } /** - * @see Graph#removeAllEdges(Collection) + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public boolean removeAllEdges(Collection edges) { + Objects.requireNonNull(edges); boolean modified = false; for (E e : edges) { @@ -94,21 +70,28 @@ public boolean removeAllEdges(Collection edges) } /** - * @see Graph#removeAllEdges(Object, Object) + * @throws UnsupportedOperationException {@inheritDoc} */ + @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) + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public boolean removeAllVertices(Collection vertices) { + Objects.requireNonNull(vertices); boolean modified = false; for (V v : vertices) { @@ -119,65 +102,55 @@ public boolean removeAllVertices(Collection vertices) } /** - * 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. + * 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 instanceof DirectedGraph)); + return toStringFromSets(vertexSet(), edgeSet(), this.getType().isDirected()); } /** - * Ensures that the specified vertex exists in this graph, or else throws - * exception. + * Ensures that the specified vertex exists in this graph, or else throws exception. * * @param v vertex * - * @return true if this assertion holds. + * @return {@code true} if this assertion holds. * - * @throws NullPointerException if specified vertex is null. - * @throws IllegalArgumentException if specified vertex does not exist in - * this graph. + * @throws NullPointerException if specified vertex is {@code null}. + * @throws IllegalArgumentException if specified vertex does not exist in this graph. */ protected boolean assertVertexExist(V v) { + Objects.requireNonNull(v); if (containsVertex(v)) { return true; - } else if (v == null) { - throw new NullPointerException(); } else { - throw new IllegalArgumentException("no such vertex in graph"); + 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. + * 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. * * @param edges edges to be removed from this graph. * - * @return true if this graph changed as a result of the call. + * @return {@code true} if this graph changed as a result of the call. + * + * @throws NullPointerException if argument is {@code null} + * @throws UnsupportedOperationException if this graph disallows modification * - * @see Graph#removeEdge(Object) - * @see Graph#containsEdge(Object) + * @see #removeAllEdges(Collection) */ - protected boolean removeAllEdges(E [] edges) + protected boolean removeAllEdges(E[] edges) { - boolean modified = false; - - for (int i = 0; i < edges.length; i++) { - modified |= removeEdge(edges[i]); - } - - return modified; + Objects.requireNonNull(edges); + return removeAllEdges(Arrays.asList(edges)); } /** @@ -185,19 +158,17 @@ protected boolean removeAllEdges(E [] edges) * * @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) + * @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) + Collection vertexSet, Collection edgeSet, boolean directed) { - List renderedEdges = new ArrayList(); + List renderedEdges = new ArrayList<>(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (E e : edgeSet) { if ((e.getClass() != DefaultEdge.class) && (e.getClass() != DefaultWeightedEdge.class)) @@ -219,13 +190,110 @@ protected String toStringFromSets( sb.append("}"); } - // REVIEW jvs 29-May-2006: dump weight somewhere? + // REVIEW jvs 29-May-2006: dump weight somewhere? renderedEdges.add(sb.toString()); sb.setLength(0); } return "(" + vertexSet + ", " + renderedEdges + ")"; } -} -// End AbstractGraph.java + /** + * 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 {@code 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 {@code 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsGraphUnion.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsGraphUnion.java new file mode 100644 index 00000000000..764792e40ea --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsGraphUnion.java @@ -0,0 +1,481 @@ +/* + * (C) Copyright 2009-2023, 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) + * + * @throws IllegalArgumentException if {@code g1 == g2} + * @throws NullPointerException if any of the arguments is {@code null} + */ + 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 + * + * @throws IllegalArgumentException if {@code g1 == g2} + * @throws NullPointerException if any of the arguments is {@code null} + */ + 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()); + } + + /** + * @throws IllegalArgumentException {@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()); + } + } + + /** + * @throws IllegalArgumentException {@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} + * + * @throws IllegalArgumentException {@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; + } + + /** + * @throws IllegalArgumentException {@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"); + } + + @Override + public GraphType getType() + { + return type; + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void setEdgeWeight(E e, double weight) + { + throw new UnsupportedOperationException(READ_ONLY); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsSubgraph.java new file mode 100644 index 00000000000..88f200170a1 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsSubgraph.java @@ -0,0 +1,634 @@ +/* + * (C) Copyright 2003-2023, 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 + * {@code 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 + * {@code equals()} methods of your vertices and edges. If you use a class that has already + * overridden the {@code equals()} method, such as {@code 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 {@code null} then all + * vertices are included. + * @param edgeSubset edges to in include in the subgraph. If {@code 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 {@code 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); + } + + /** + * @throws UnsupportedOperationException always + */ + @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 {@code true} if the vertex was added, otherwise {@code false}. + * + * @throws NullPointerException {@inheritDoc} + * @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) { + if (vertexSet.size() > base.edgeSet().size()) { + base + .edgeSet().stream() + .filter( + e -> vertexSet.contains(base.getEdgeSource(e)) + && vertexSet.contains(base.getEdgeTarget(e))) + .forEach(edgeSet::add); + } else { + vertexSet.forEach(v -> + base.edgesOf(v).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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUndirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUndirectedGraph.java index 208441b30a0..9b8be72b395 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUndirectedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUndirectedGraph.java @@ -1,111 +1,79 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * AsUndirectedGraph.java - * ---------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 14-Aug-2003 : Initial revision (JVS); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 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). + * 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 + * {@link 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.

    + *

    + * 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.

    + *

    + * This graph does not pass the hashCode and equals operations through to the backing graph, + * but relies on {@code Object}'s {@code equals} and {@code 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 - * @since Aug 14, 2003 */ public class AsUndirectedGraph extends GraphDelegator - implements Serializable, - UndirectedGraph + implements Serializable, Graph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3257845485078065462L; // @todo renew - private static final String NO_EDGE_ADD = - "this graph does not support edge addition"; - private static final String UNDIRECTED = - "this graph only supports undirected operations"; + private static final long serialVersionUID = 325983813283133557L; - //~ Constructors ----------------------------------------------------------- + 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. + * @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(DirectedGraph g) + public AsUndirectedGraph(Graph g) { super(g); + GraphTests.requireDirected(g); } - //~ Methods ---------------------------------------------------------------- - /** - * @see Graph#getAllEdges(Object, Object) + * {@inheritDoc} */ + @Override public Set getAllEdges(V sourceVertex, V targetVertex) { Set forwardList = super.getAllEdges(sourceVertex, targetVertex); @@ -116,9 +84,7 @@ public Set getAllEdges(V sourceVertex, V targetVertex) } Set reverseList = super.getAllEdges(targetVertex, sourceVertex); - Set list = - new ArrayUnenforcedSet( - forwardList.size() + reverseList.size()); + Set list = new ArrayUnenforcedSet<>(forwardList.size() + reverseList.size()); list.addAll(forwardList); list.addAll(reverseList); @@ -126,8 +92,9 @@ public Set getAllEdges(V sourceVertex, V targetVertex) } /** - * @see Graph#getEdge(Object, Object) + * {@inheritDoc} */ + @Override public E getEdge(V sourceVertex, V targetVertex) { E edge = super.getEdge(sourceVertex, targetVertex); @@ -141,69 +108,87 @@ public E getEdge(V sourceVertex, V targetVertex) } /** - * @see Graph#addEdge(Object, Object) + * {@inheritDoc} + * + * @throws UnsupportedOperationException always, since operation is unsupported */ + @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(NO_EDGE_ADD); } /** - * @see Graph#addEdge(Object, Object, Object) + * {@inheritDoc} + * + * @throws UnsupportedOperationException always, since operation is unsupported */ + @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { throw new UnsupportedOperationException(NO_EDGE_ADD); } /** - * @see UndirectedGraph#degreeOf(Object) + * {@inheritDoc} */ + @Override public int degreeOf(V vertex) { - // this counts loops twice, which is consistent with AbstractBaseGraph - return super.inDegreeOf(vertex) + super.outDegreeOf(vertex); + return super.degreeOf(vertex); } /** - * @see DirectedGraph#inDegreeOf(Object) + * {@inheritDoc} */ + @Override + public Set incomingEdgesOf(V vertex) + { + return super.edgesOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override public int inDegreeOf(V vertex) { - throw new UnsupportedOperationException(UNDIRECTED); + return super.degreeOf(vertex); } /** - * @see DirectedGraph#incomingEdgesOf(Object) + * {@inheritDoc} */ - public Set incomingEdgesOf(V vertex) + @Override + public Set outgoingEdgesOf(V vertex) { - throw new UnsupportedOperationException(UNDIRECTED); + return super.edgesOf(vertex); } /** - * @see DirectedGraph#outDegreeOf(Object) + * {@inheritDoc} */ + @Override public int outDegreeOf(V vertex) { - throw new UnsupportedOperationException(UNDIRECTED); + return super.degreeOf(vertex); } /** - * @see DirectedGraph#outgoingEdgesOf(Object) + * {@inheritDoc} */ - public Set outgoingEdgesOf(V vertex) + @Override + public GraphType getType() { - throw new UnsupportedOperationException(UNDIRECTED); + return super.getType().asUndirected(); } /** - * @see AbstractBaseGraph#toString() + * {@inheritDoc} */ + @Override public String toString() { return super.toStringFromSets(vertexSet(), edgeSet(), false); } } - -// End AsUndirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnmodifiableGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnmodifiableGraph.java new file mode 100644 index 00000000000..f51a8f63cf2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnmodifiableGraph.java @@ -0,0 +1,180 @@ +/* + * (C) Copyright 2003-2023, 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 + * {@link UnsupportedOperationException}. + * + *

    + * This graph does not pass the hashCode and equals operations through to the backing graph, + * but relies on {@code Object}'s {@code equals} and {@code 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 + * + * @throws NullPointerException if argument is {@code null} + */ + public AsUnmodifiableGraph(Graph g) + { + super(g); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E addEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addEdge(V sourceVertex, V targetVertex, E e) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public V addVertex() + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addVertex(V v) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeAllEdges(Collection edges) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public Set removeAllEdges(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeAllVertices(Collection vertices) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeEdge(E e) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E removeEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeVertex(V v) + { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * {@inheritDoc} + */ + @Override + public GraphType getType() + { + return super.getType().asUnmodifiable(); + } + + /** + * @throws UnsupportedOperationException always + * + * @since 1.5.3 + */ + @Override + public void setEdgeWeight(E e, double weight) { + throw new UnsupportedOperationException(UNMODIFIABLE); + } + + /** + * @throws UnsupportedOperationException always + * + * @since 1.5.3 + */ + @Override + public void setEdgeWeight(V sourceVertex, V targetVertex, double weight) { + throw new UnsupportedOperationException(UNMODIFIABLE); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedDirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedDirectedGraph.java deleted file mode 100644 index be56777f213..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedDirectedGraph.java +++ /dev/null @@ -1,100 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * AsUnweightedGraph.java - * ---------------------- - * (C) Copyright 2007-2008, by Lucas J. Scharenbroich and Contributors. - * - * Original Author: Lucas J. Scharenbroich - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 7-Sep-2007 : Initial revision (LJS); - * - */ -package org.jgrapht.graph; - -import java.io.*; - -import org.jgrapht.*; - - -/** - * An unweighted view of the backing weighted graph specified in the - * constructor. This graph allows modules to apply algorithms designed for - * unweighted graphs to a weighted graph by simply ignoring edge weights. Query - * operations on this graph "read through" to the backing graph. Vertex - * addition/removal and edge addition/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.

    - * - *

    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.

    - * - * @author Lucas J. Scharenbroich - * @since Sep 7, 2007 - */ -public class AsUnweightedDirectedGraph - extends GraphDelegator - implements Serializable, - DirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -4320818446777715312L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructor for AsUnweightedGraph. - * - * @param g the backing graph over which an unweighted view is to be - * created. - */ - public AsUnweightedDirectedGraph(DirectedGraph g) - { - super(g); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see Graph#getEdgeWeight - */ - public double getEdgeWeight(E e) - { - return WeightedGraph.DEFAULT_EDGE_WEIGHT; - } -} - -// End AsUnweightedDirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedGraph.java index 7ee6af915e4..77123612866 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedGraph.java @@ -1,100 +1,79 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2018, by Lukas Harzenetter and Contributors. * - * 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. - */ -/* ---------------------- - * AsUnweightedGraph.java - * ---------------------- - * (C) Copyright 2007-2008, by Lucas J. Scharenbroich and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Lucas J. Scharenbroich - * Contributor(s): John V. Sichi -* - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 7-Sep-2007 : Initial revision (LJS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 org.jgrapht.*; +import java.io.*; +import java.util.*; /** - * An unweighted view of the backing weighted graph specified in the - * constructor. This graph allows modules to apply algorithms designed for - * unweighted graphs to a weighted graph by simply ignoring edge weights. Query - * operations on this graph "read through" to the backing graph. Vertex - * addition/removal and edge addition/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.

    + * Provides an unweighted view on a graph. * - *

    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.

    + * Algorithms designed for unweighted graphs should also work on weighted graphs. This class + * emulates an unweighted graph based on a weighted one by returning {@link 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, {@link 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. * - * @author Lucas J. Scharenbroich - * @since Sep 7, 2007 + * @param the graph vertex type + * @param the graph edge type */ - public class AsUnweightedGraph extends GraphDelegator - implements Serializable + implements Serializable, Graph { - //~ Static fields/initializers --------------------------------------------- - /** - */ - private static final long serialVersionUID = 7175505077601824663L; - - //~ Constructors ----------------------------------------------------------- + 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 graph over which an unweighted view is to be - * created. + * @param g the backing directed graph over which an undirected view is to be created. + * @throws NullPointerException if the graph is {@code null} */ public AsUnweightedGraph(Graph g) { - super(g); + super(Objects.requireNonNull(g)); } - //~ Methods ---------------------------------------------------------------- + @Override + public double getEdgeWeight(E e) + { + return Graph.DEFAULT_EDGE_WEIGHT; + } /** - * @see Graph#getEdgeWeight + * @throws UnsupportedOperationException always */ - public double getEdgeWeight(E e) + @Override + public void setEdgeWeight(E e, double weight) { - return WeightedGraph.DEFAULT_EDGE_WEIGHT; + throw new UnsupportedOperationException(EDGE_WEIGHT_IS_NOT_SUPPORTED); } -} -// End AsUnweightedGraph.java + @Override + public GraphType getType() + { + return super.getType().asUnweighted(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/AsWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/AsWeightedGraph.java index 54fbaf1db98..e955f18fd37 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/AsWeightedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/AsWeightedGraph.java @@ -1,147 +1,213 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * AsWeightedGraph.java - * ---------------------- - * (C) Copyright 2007, by Lucas J. Scharenbroich and Contributors. +/* + * (C) Copyright 2018-2023, by Lukas Harzenetter and Contributors. * - * Original Author: Lucas J. Scharenbroich - * Contributor(s): John V. Sichi + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Sep-2007 : Initial revision (LJS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 org.jgrapht.*; +import java.io.*; +import java.util.*; +import java.util.function.*; /** - *

    A weighted view of the backing graph specified in the constructor. This - * graph allows modules to apply algorithms designed for weighted graphs to an - * unweighted graph by providing an explicit edge weight mapping. The - * implementation also allows for "masking" weights for a subset of the edges in - * an existing weighted graph.

    + * Provides a weighted view of a graph. The class stores edge weights internally. + * All {@link #getEdgeWeight(Object)} calls are handled by this view; all other graph operations are + * propagated to the graph backing this view. * - *

    Query operations on this graph "read through" to the backing graph. Vertex - * addition/removal and edge addition/removal are all supported (and immediately - * reflected in the backing graph). Setting an edge weight will pass the - * operation to the backing graph as well if the backing graph implements the - * WeightedGraph interface. Setting an edge weight will modify the weight map in - * order to maintain a consistent graph.

    + *

    + * 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. * - *

    Note that edges returned by this graph's accessors are really just the - * edges of the underlying directed 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(Object)} call is + * propagated to the backing graph. * - *

    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.

    + * Finally, the view provides a {@link #setEdgeWeight(Object, double)} method. This method behaves differently + * depending on how the view is constructed. See {@link #setEdgeWeight(Object, double)} for details. * - * @author Lucas J. Scharenbroich - * @since Sep 10, 2007 + * @param the graph vertex type + * @param the graph edge type */ public class AsWeightedGraph extends GraphDelegator - implements Serializable, - WeightedGraph + implements Serializable, Graph { - //~ Static fields/initializers --------------------------------------------- + + 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(Object, double)} method will update the map. Moreover, calls to + * {@link #setEdgeWeight(Object, double)} 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 either one of the arguments is {@code null} */ - private static final long serialVersionUID = -716810639338971372L; - - //~ Instance fields -------------------------------------------------------- - - protected final Map weightMap; - private final boolean isWeightedGraph; - - //~ Constructors ----------------------------------------------------------- + public AsWeightedGraph(Graph graph, Map weights) + { + this(graph, weights, graph.getType().isWeighted()); + } /** - * Constructor for AsWeightedGraph. + * Constructor for AsWeightedGraph which allows weight write propagation to be requested + * explicitly. * - * @param g the backing graph over which a weighted view is to be created. - * @param weightMap A mapping of edges to weights. If an edge is not present - * in the weight map, the edge weight for the underlying graph is returned. - * Note that a live reference to this map is retained, so if the caller - * changes the map after construction, the changes will affect the - * AsWeightedGraph instance as well. + * @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 {@code setEdgeWeight()} method. + * + * @throws NullPointerException if either one of {@code graph} or {@code weight} is {@code null} + * @throws IllegalArgumentException if {@code writeWeightsThrough} is set to true and + * {@code graph} is not a weighted graph */ - public AsWeightedGraph(Graph g, Map weightMap) + public AsWeightedGraph(Graph graph, Map weights, boolean writeWeightsThrough) { - super(g); - assert (weightMap != null); - this.weightMap = weightMap; - - // Remember whether the backing graph implements the WeightedGraph - // interface - this.isWeightedGraph = (g instanceof WeightedGraph); + super(graph); + this.weights = Objects.requireNonNull(weights); + this.weightFunction = null; + this.cacheWeights = false; + this.writeWeightsThrough = writeWeightsThrough; + + if (this.writeWeightsThrough) + GraphTests.requireWeighted(graph); } - //~ Methods ---------------------------------------------------------------- + /** + * 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 + * {@code cacheWeights} is set to {@code true}, the weight of an edge returned by the + * {@code weightFunction} after its first invocation is stored in a map. The weight of an + * edge returned by subsequent calls to {@link #getEdgeWeight(Object)} for the same edge will then be + * retrieved directly from the map, instead of re-invoking the weight function. If + * {@code cacheWeights} is set to {@code false}, each invocation of + * the {@link #getEdgeWeight(Object)} 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 {@code true}, weights are cached once computed by the + * weight function + * @param writeWeightsThrough if set to {@code true}, the weight set directly by + * the {@link #setEdgeWeight(Object, double)} method will be propagated to the backing graph. + * + * @throws NullPointerException if either one of {@code graph} or {@code weightFunction} is {@code null} + * @throws IllegalArgumentException if {@code writeWeightsThrough} is set to true and + * {@code 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); + } /** - * @see WeightedGraph#setEdgeWeight + * 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(Object)} 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 {@code cacheWeights} is set to {@code 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 {@inheritDoc} */ - public void setEdgeWeight(E e, double weight) + @Override + public double getEdgeWeight(E e) { - if (isWeightedGraph) { - super.setEdgeWeight(e, weight); + 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); } - // Always modify the weight map. It would be a terrible violation - // of the use contract to silently ignore changes to the weights. - weightMap.put(e, weight); + if (Objects.isNull(weight)) + weight = super.getEdgeWeight(e); + + return weight; } /** - * @see Graph#getEdgeWeight + * Assigns a weight to an edge. If {@code writeWeightsThrough} is set to {@code 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 {@code 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 {@inheritDoc} + * @throws UnsupportedOperationException if a weight function is used and caching is disabled */ - public double getEdgeWeight(E e) + @Override + public void setEdgeWeight(E e, double weight) { - double weight; + assert e != null; - // Always return the value from the weight map first and - // only pass the call through as a backup - if (weightMap.containsKey(e)) { - weight = weightMap.get(e); - } else { - weight = super.getEdgeWeight(e); + if (weightFunction != null && !cacheWeights) { + throw new UnsupportedOperationException( + "Cannot set an edge weight when a weight function is used and caching is disabled"); } - return weight; + this.weights.put(e, weight); + + if (this.writeWeightsThrough) + this.getDelegate().setEdgeWeight(e, weight); } -} -// End AsWeightedGraph.java + @Override + public GraphType getType() + { + return super.getType().asWeighted(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/BaseIntrusiveEdgesSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/BaseIntrusiveEdgesSpecifics.java new file mode 100644 index 00000000000..38a357e4333 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/BaseIntrusiveEdgesSpecifics.java @@ -0,0 +1,172 @@ +/* + * (C) Copyright 2003-2023, 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); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedEdgeFactory.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedEdgeFactory.java deleted file mode 100644 index 59a7dc982ab..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedEdgeFactory.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * ClassBasedEdgeFactory.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 04-Aug-2003 : Renamed from EdgeFactoryFactory & made utility class (BN); - * 03-Nov-2003 : Made edge factories serializable (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import java.io.*; - -import org.jgrapht.*; - - -/** - * An {@link EdgeFactory} for producing edges by using a class as a factory. - * - * @author Barak Naveh - * @since Jul 14, 2003 - */ -public class ClassBasedEdgeFactory - implements EdgeFactory, - Serializable -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3618135658586388792L; - - //~ Instance fields -------------------------------------------------------- - - private final Class edgeClass; - - //~ Constructors ----------------------------------------------------------- - - public ClassBasedEdgeFactory(Class edgeClass) - { - this.edgeClass = edgeClass; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see EdgeFactory#createEdge(Object, Object) - */ - public E createEdge(V source, V target) - { - try { - return edgeClass.newInstance(); - } catch (Exception ex) { - throw new RuntimeException("Edge factory failed", ex); - } - } -} - -// End ClassBasedEdgeFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedVertexFactory.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedVertexFactory.java deleted file mode 100644 index 8b2e5ff0910..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ClassBasedVertexFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * ClassBasedVertexFactory.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * A {@link VertexFactory} for producing vertices by using a class as a factory. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class ClassBasedVertexFactory - implements VertexFactory -{ - //~ Instance fields -------------------------------------------------------- - - private final Class vertexClass; - - //~ Constructors ----------------------------------------------------------- - - public ClassBasedVertexFactory(Class vertexClass) - { - this.vertexClass = vertexClass; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see VertexFactory#createVertex() - */ - public V createVertex() - { - try { - return this.vertexClass.newInstance(); - } catch (Exception e) { - throw new RuntimeException("Vertex factory failed", e); - } - } -} - -// End ClassBasedVertexFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedGraph.java index 5f841a9db46..613bcc6eefb 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedGraph.java @@ -1,86 +1,94 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * DefaultDirectedGraph.java - * ------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A directed graph. A default directed graph is a non-simple directed graph in - * which multiple edges between any two vertices are not permitted, but + * 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. - * - *

    prefixed 'Default' to avoid name collision with the DirectedGraph - * interface.

    + * + * @param the graph vertex type + * @param the graph edge type + * */ public class DefaultDirectedGraph extends AbstractBaseGraph - implements DirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -2066644490824847621L; - private static final long serialVersionUID = 3544953246956466230L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public DefaultDirectedGraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * Creates a new directed graph. - * + * 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 DefaultDirectedGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new DefaultDirectedGraph<>(edgeClass)); } /** - * Creates a new directed graph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 DefaultDirectedGraph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef, false, true); + return new GraphBuilder<>(new DefaultDirectedGraph<>(null, edgeSupplier, false)); } -} -// End DefaultDirectedGraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedWeightedGraph.java index b069554bff1..fccdf13055b 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedWeightedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedWeightedGraph.java @@ -1,85 +1,91 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------------------- - * DefaultDirectedWeightedGraph.java - * --------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Jun-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A directed weighted graph. A directed weighted graph is a non-simple directed - * graph in which multiple edges between any two vertices are not - * permitted, but loops are. The graph has weights on its edges. + * 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 - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -4867672646995721544L; - private static final long serialVersionUID = 3761405317841171513L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public DefaultDirectedWeightedGraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass)); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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); + } /** - * Creates a new directed weighted graph. - * + * 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 DefaultDirectedWeightedGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new DefaultDirectedWeightedGraph<>(edgeClass)); } /** - * Creates a new directed weighted graph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 DefaultDirectedWeightedGraph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef); + return new GraphBuilder<>(new DefaultDirectedWeightedGraph<>(null, edgeSupplier)); } -} -// End DefaultDirectedWeightedGraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdge.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdge.java index 1d7422c1df7..88966aa10b5 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdge.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdge.java @@ -1,68 +1,37 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * DefaultEdge.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 10-Aug-2003 : General edge refactoring (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 - * @since Jul 14, 2003 */ public class DefaultEdge extends IntrusiveEdge { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3258408452177932855L; - //~ Methods ---------------------------------------------------------------- - /** - * Retrieves the source of this edge. This is protected, for use by - * subclasses only (e.g. for implementing toString). + * Retrieves the source of this edge. This is protected, for use by subclasses only (e.g. for + * implementing toString). * * @return source of this edge */ @@ -72,8 +41,8 @@ protected Object getSource() } /** - * Retrieves the target of this edge. This is protected, for use by - * subclasses only (e.g. for implementing toString). + * Retrieves the target of this edge. This is protected, for use by subclasses only (e.g. for + * implementing toString). * * @return target of this edge */ @@ -82,10 +51,9 @@ protected Object getTarget() return target; } + @Override public String toString() { return "(" + source + " : " + target + ")"; } } - -// End DefaultEdge.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdgeFunction.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdgeFunction.java new file mode 100644 index 00000000000..e7ed17d4f73 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdgeFunction.java @@ -0,0 +1,99 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if argument is {@code null} + */ + public DefaultEdgeFunction(T defaultValue) + { + this(defaultValue, new HashMap<>()); + } + + /** + * Create a new function + * + * @param defaultValue the default value + * @param map the underlying map + * + * @throws NullPointerException if either one of the arguments is {@code null} + */ + 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphIterables.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphIterables.java new file mode 100644 index 00000000000..c0a23d258e7 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphIterables.java @@ -0,0 +1,66 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphMapping.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphMapping.java index 87389067edc..2aada09b655 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphMapping.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphMapping.java @@ -1,84 +1,55 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * 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. - */ -/* ----------------- - * DefaultGraphMapping.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Assaf Lehr - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; -import java.util.*; - 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 - * hashmaps will provide O(1) performence. + * Implementation of the GraphMapping interface. The performance of {@code 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. * - * @author Assaf - * @since Jul 30, 2005 + * @param the graph vertex type + * @param the graph edge type + * + * @author Assaf Lehr */ public class DefaultGraphMapping implements GraphMapping { - //~ Instance fields -------------------------------------------------------- - private Map graphMappingForward; private Map graphMappingReverse; private Graph graph1; private Graph graph2; - //~ Constructors ----------------------------------------------------------- - /** - * 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. + * 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 - * @param g2ToG1 - * @param g1 - * @param g2 + * @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) + public DefaultGraphMapping(Map g1ToG2, Map g2ToG1, Graph g1, Graph g2) { this.graph1 = g1; this.graph2 = g2; @@ -86,8 +57,7 @@ public DefaultGraphMapping( this.graphMappingReverse = g2ToG1; } - //~ Methods ---------------------------------------------------------------- - + @Override public E getEdgeCorrespondence(E currEdge, boolean forward) { Graph sourceGraph, targetGraph; @@ -101,25 +71,18 @@ public E getEdgeCorrespondence(E currEdge, boolean forward) } V mappedSourceVertex = - getVertexCorrespondence( - sourceGraph.getEdgeSource(currEdge), - forward); + getVertexCorrespondence(sourceGraph.getEdgeSource(currEdge), forward); V mappedTargetVertex = - getVertexCorrespondence( - sourceGraph.getEdgeTarget(currEdge), - forward); + getVertexCorrespondence(sourceGraph.getEdgeTarget(currEdge), forward); if ((mappedSourceVertex == null) || (mappedTargetVertex == null)) { return null; } else { - return targetGraph.getEdge( - mappedSourceVertex, - mappedTargetVertex); + return targetGraph.getEdge(mappedSourceVertex, mappedTargetVertex); } } - public V getVertexCorrespondence( - V keyVertex, - boolean forward) + @Override + public V getVertexCorrespondence(V keyVertex, boolean forward) { Map graphMapping; if (forward) { @@ -131,5 +94,3 @@ public V getVertexCorrespondence( return graphMapping.get(keyVertex); } } - -// End DefaultGraphMapping.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphSpecificsStrategy.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphSpecificsStrategy.java new file mode 100644 index 00000000000..dbfff0bfba8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphSpecificsStrategy.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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()); + } + }; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphType.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphType.java new file mode 100644 index 00000000000..e53adc6e8cd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphType.java @@ -0,0 +1,454 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultListenableGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultListenableGraph.java index d13173268ff..638a2045c62 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultListenableGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultListenableGraph.java @@ -1,98 +1,65 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* --------------------------- - * DefaultListenableGraph.java - * --------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 04-Aug-2003 : Strong refs to listeners instead of weak refs (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 07-Mar-2004 : Fixed unnecessary clone bug #819075 (BN); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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. + * A graph backed by the the graph specified at the constructor, which can be listened by + * {@code GraphListener} s and by {@code 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 {@code Object}'s {@code equals} and {@code hashCode} methods. + *

    * - *

    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 - * @since Jul 20, 2003 */ public class DefaultListenableGraph extends GraphDelegator - implements ListenableGraph, - Cloneable + implements ListenableGraph, Cloneable { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3977575900898471984L; + private static final long serialVersionUID = -1156773351121025002L; - //~ Instance fields -------------------------------------------------------- - - private List> graphListeners = - new ArrayList>(); - private List> vertexSetListeners = - new ArrayList>(); + private List> graphListeners = new ArrayList<>(); + private List> vertexSetListeners = new ArrayList<>(); private FlyweightEdgeEvent reuseableEdgeEvent; private FlyweightVertexEvent reuseableVertexEvent; private boolean reuseEvents; - //~ Constructors ----------------------------------------------------------- - /** * Creates a new listenable graph. * * @param g the backing graph. + * + * @throws IllegalArgumentException if the backing graph is already a listenable graph. + * @throws NullPointerException if {@code g} is {@code null} */ public DefaultListenableGraph(Graph g) { @@ -100,43 +67,38 @@ public DefaultListenableGraph(Graph g) } /** - * 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. + * Creates a new listenable graph. If the {@code reuseEvents} flag is set to + * {@code 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. + * @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. + * @throws IllegalArgumentException if the backing graph is already a listenable graph. + * @throws NullPointerException if {@code g} is {@code null} */ public DefaultListenableGraph(Graph g, boolean reuseEvents) { super(g); this.reuseEvents = reuseEvents; - reuseableEdgeEvent = new FlyweightEdgeEvent(this, -1, null); - reuseableVertexEvent = new FlyweightVertexEvent(this, -1, null); + 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"); + throw new IllegalArgumentException("base graph cannot be listenable"); } } - //~ Methods ---------------------------------------------------------------- - /** - * 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. + * If the {@code reuseEvents} flag is set to {@code 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. + * @param reuseEvents whether to reuse previously fired event objects instead of creating a new + * event object for each event. */ public void setReuseEvents(boolean reuseEvents) { @@ -144,58 +106,59 @@ public void setReuseEvents(boolean 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. + * Tests whether the {@code reuseEvents} flag is set. If the flag is set to + * {@code 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. + * @return the value of the {@code reuseEvents} flag. */ public boolean isReuseEvents() { return reuseEvents; } - /** - * @see Graph#addEdge(Object, Object) - */ + @Override public E addEdge(V sourceVertex, V targetVertex) { E e = super.addEdge(sourceVertex, targetVertex); if (e != null) { - fireEdgeAdded(e, sourceVertex, targetVertex); + fireEdgeAdded(e, sourceVertex, targetVertex, Graph.DEFAULT_EDGE_WEIGHT); } return e; } - /** - * @see Graph#addEdge(Object, Object, Object) - */ + @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { boolean added = super.addEdge(sourceVertex, targetVertex, e); if (added) { - fireEdgeAdded(e, sourceVertex, targetVertex); + fireEdgeAdded(e, sourceVertex, targetVertex, Graph.DEFAULT_EDGE_WEIGHT); } return added; } - /** - * @see ListenableGraph#addGraphListener(GraphListener) - */ + @Override public void addGraphListener(GraphListener l) { addToListenerList(graphListeners, l); } - /** - * @see Graph#addVertex(Object) - */ + @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); @@ -207,84 +170,70 @@ public boolean addVertex(V v) return modified; } - /** - * @see ListenableGraph#addVertexSetListener(VertexSetListener) - */ + @Override public void addVertexSetListener(VertexSetListener l) { addToListenerList(vertexSetListeners, l); } - /** - * @see java.lang.Object#clone() - */ + @Override public Object clone() { try { - TypeUtil> typeDecl = null; - - DefaultListenableGraph g = - TypeUtil.uncheckedCast(super.clone(), typeDecl); - g.graphListeners = new ArrayList>(); - g.vertexSetListeners = new ArrayList>(); + 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"); + throw new Error("internal error", e); } } - /** - * @see Graph#removeEdge(Object, Object) - */ + @Override public E removeEdge(V sourceVertex, V targetVertex) { - E e = super.removeEdge(sourceVertex, targetVertex); - + E e = super.getEdge(sourceVertex, targetVertex); if (e != null) { - fireEdgeRemoved(e, sourceVertex, targetVertex); + double weight = super.getEdgeWeight(e); + if (super.removeEdge(e)) { + fireEdgeRemoved(e, sourceVertex, targetVertex, weight); + } } - return e; } - /** - * @see Graph#removeEdge(Object) - */ + @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); + fireEdgeRemoved(e, sourceVertex, targetVertex, weight); } return modified; } - /** - * @see ListenableGraph#removeGraphListener(GraphListener) - */ + @Override public void removeGraphListener(GraphListener l) { graphListeners.remove(l); } - /** - * @see Graph#removeVertex(Object) - */ + @Override public boolean removeVertex(V v) { if (containsVertex(v)) { Set touchingEdgesList = edgesOf(v); // copy set to avoid ConcurrentModificationException - removeAllEdges(new ArrayList(touchingEdgesList)); + removeAllEdges(new ArrayList<>(touchingEdgesList)); super.removeVertex(v); // remove the vertex itself @@ -296,9 +245,18 @@ public boolean removeVertex(V v) } } - /** - * @see ListenableGraph#removeVertexSetListener(VertexSetListener) - */ + @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); @@ -308,17 +266,14 @@ public void removeVertexSetListener(VertexSetListener 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) + protected void fireEdgeAdded(E edge, V source, V target, double weight) { - GraphEdgeChangeEvent e = - createGraphEdgeChangeEvent( - GraphEdgeChangeEvent.EDGE_ADDED, - edge, source, target); + GraphEdgeChangeEvent e = createGraphEdgeChangeEvent( + GraphEdgeChangeEvent.EDGE_ADDED, edge, source, target, weight); for (GraphListener l : graphListeners) { l.edgeAdded(e); @@ -329,23 +284,38 @@ protected void fireEdgeAdded(E edge, V source, V target) * 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) + protected void fireEdgeRemoved(E edge, V source, V target, double weight) { - GraphEdgeChangeEvent e = - createGraphEdgeChangeEvent( - GraphEdgeChangeEvent.EDGE_REMOVED, - edge, source, target); + 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. * @@ -354,9 +324,7 @@ protected void fireEdgeRemoved(E edge, V source, V target) protected void fireVertexAdded(V vertex) { GraphVertexChangeEvent e = - createGraphVertexChangeEvent( - GraphVertexChangeEvent.VERTEX_ADDED, - vertex); + createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_ADDED, vertex); for (VertexSetListener l : vertexSetListeners) { l.vertexAdded(e); @@ -375,9 +343,7 @@ protected void fireVertexAdded(V vertex) protected void fireVertexRemoved(V vertex) { GraphVertexChangeEvent e = - createGraphVertexChangeEvent( - GraphVertexChangeEvent.VERTEX_REMOVED, - vertex); + createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_REMOVED, vertex); for (VertexSetListener l : vertexSetListeners) { l.vertexRemoved(e); @@ -388,9 +354,7 @@ protected void fireVertexRemoved(V vertex) } } - private static void addToListenerList( - List list, - L l) + private static void addToListenerList(List list, L l) { if (!list.contains(l)) { list.add(l); @@ -398,24 +362,22 @@ private static void addToListenerList( } private GraphEdgeChangeEvent createGraphEdgeChangeEvent( - int eventType, E edge, V source, V target) + 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); + return new GraphEdgeChangeEvent<>(this, eventType, edge, source, target, weight); } } - private GraphVertexChangeEvent createGraphVertexChangeEvent( - int eventType, - V vertex) + private GraphVertexChangeEvent createGraphVertexChangeEvent(int eventType, V vertex) { if (reuseEvents) { reuseableVertexEvent.setType(eventType); @@ -423,17 +385,14 @@ private GraphVertexChangeEvent createGraphVertexChangeEvent( return reuseableVertexEvent; } else { - return new GraphVertexChangeEvent(this, eventType, vertex); + return new GraphVertexChangeEvent<>(this, eventType, vertex); } } - //~ Inner Classes ---------------------------------------------------------- - /** * A reuseable edge event. * * @author Barak Naveh - * @since Aug 10, 2003 */ private static class FlyweightEdgeEvent extends GraphEdgeChangeEvent @@ -441,11 +400,11 @@ private static class FlyweightEdgeEvent private static final long serialVersionUID = 3907207152526636089L; /** - * @see GraphEdgeChangeEvent#GraphEdgeChangeEvent(Object, int, Edge) + * @see GraphEdgeChangeEvent */ public FlyweightEdgeEvent(Object eventSource, int type, EE e) { - super(eventSource, type, e); + super(eventSource, type, e, null, null); } /** @@ -462,12 +421,17 @@ 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. * @@ -483,7 +447,6 @@ protected void setType(int type) * A reuseable vertex event. * * @author Barak Naveh - * @since Aug 10, 2003 */ private static class FlyweightVertexEvent extends GraphVertexChangeEvent @@ -491,8 +454,7 @@ private static class FlyweightVertexEvent private static final long serialVersionUID = 3257848787857585716L; /** - * @see GraphVertexChangeEvent#GraphVertexChangeEvent(Object, int, - * Object) + * @see GraphVertexChangeEvent#GraphVertexChangeEvent(Object, int, Object) */ public FlyweightVertexEvent(Object eventSource, int type, VV vertex) { @@ -519,6 +481,5 @@ protected void setVertex(VV vertex) this.vertex = vertex; } } -} -// End DefaultListenableGraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedGraph.java new file mode 100644 index 00000000000..f76a6d3de02 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedGraph.java @@ -0,0 +1,93 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedWeightedGraph.java new file mode 100644 index 00000000000..7ee7813052c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedWeightedGraph.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultWeightedEdge.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultWeightedEdge.java index 172fe9d7ff4..b867b283c3f 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultWeightedEdge.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultWeightedEdge.java @@ -1,70 +1,58 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * DefaultWeightedEdge.java - * ---------------- - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2006-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 29-May-2006 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 WeightedGraph}. 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. + * 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 DefaultEdge + extends IntrusiveWeightedEdge { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 229708706467350994L; + private static final long serialVersionUID = -3259071493169286685L; - //~ Instance fields -------------------------------------------------------- - - double weight = WeightedGraph.DEFAULT_EDGE_WEIGHT; + /** + * 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; + } - //~ Methods ---------------------------------------------------------------- + /** + * 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). + * Retrieves the weight of this edge. This is protected, for use by subclasses only (e.g. for + * implementing toString). * * @return weight of this edge */ @@ -72,6 +60,11 @@ protected double getWeight() { return weight; } -} -// End DefaultWeightedEdge.java + @Override + public String toString() + { + return "(" + source + " : " + target + ")"; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedAcyclicGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedAcyclicGraph.java new file mode 100644 index 00000000000..656779212dd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedAcyclicGraph.java @@ -0,0 +1,1291 @@ +/* + * (C) Copyright 2008-2023, 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 java.io.*; +import java.util.*; +import java.util.function.*; + +import org.jgrapht.graph.builder.*; +import org.jgrapht.traverse.*; +import org.jgrapht.util.*; + +/** + * 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 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 weighted if true the graph will be weighted, otherwise not + * @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not + * @param graphSpecificsStrategy strategy for constructing low-level graph specifics + */ + public DirectedAcyclicGraph( + Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted, + boolean allowMultipleEdges, GraphSpecificsStrategy graphSpecificsStrategy) + { + this( + vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(), + weighted, allowMultipleEdges, graphSpecificsStrategy); + } + + /** + * 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) + { + this( + vertexSupplier, edgeSupplier, visitedStrategyFactory, topoOrderMap, weighted, + allowMultipleEdges, new FastLookupGraphSpecificsStrategy<>()); + } + + /** + * 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 + * @param graphSpecificsStrategy strategy for constructing low-level graph specifics + */ + protected DirectedAcyclicGraph( + Supplier vertexSupplier, Supplier edgeSupplier, + VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap, + boolean weighted, boolean allowMultipleEdges, + GraphSpecificsStrategy graphSpecificsStrategy) + { + super( + vertexSupplier, edgeSupplier, + new DefaultGraphType.Builder() + .directed().allowMultipleEdges(allowMultipleEdges).allowSelfLoops(false) + .weighted(weighted).allowCycles(false).build(), + graphSpecificsStrategy); + 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 vertex is not in the graph + * @throws GraphCycleProhibitedException if the vertex would induce a cycle in the graph + */ + @Override + public E addEdge(V sourceVertex, V targetVertex) + { + assertVertexExist(sourceVertex); + assertVertexExist(targetVertex); + + try { + updateDag(sourceVertex, targetVertex); + return super.addEdge(sourceVertex, targetVertex); + } catch (CycleFoundException e) { + throw new GraphCycleProhibitedException(); + } + } + + /** + * {@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 vertex is not in the graph + * @throws GraphCycleProhibitedException if the vertex 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); + + try { + updateDag(sourceVertex, targetVertex); + return super.addEdge(sourceVertex, targetVertex, e); + } catch (CycleFoundException ex) { + throw new GraphCycleProhibitedException(); + } + } + + /** + * 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 + */ + @Override + 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedGraphUnion.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedGraphUnion.java deleted file mode 100644 index b151370790a..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedGraphUnion.java +++ /dev/null @@ -1,109 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * DirectedGraphUnion.java - * ------------------------- - * (C) Copyright 2009-2009, by Ilya Razenshteyn - * - * Original Author: Ilya Razenshteyn and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 02-Feb-2009 : Initial revision (IR); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -public class DirectedGraphUnion - extends GraphUnion> - implements DirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = -740199233080172450L; - - //~ Constructors ----------------------------------------------------------- - - public DirectedGraphUnion( - DirectedGraph g1, - DirectedGraph g2, - WeightCombiner operator) - { - super(g1, g2, operator); - } - - public DirectedGraphUnion(DirectedGraph g1, DirectedGraph g2) - { - super(g1, g2); - } - - //~ Methods ---------------------------------------------------------------- - - public int inDegreeOf(V vertex) - { - Set res = incomingEdgesOf(vertex); - return res.size(); - } - - public Set incomingEdgesOf(V vertex) - { - Set res = new HashSet(); - if (getG1().containsVertex(vertex)) { - res.addAll(getG1().incomingEdgesOf(vertex)); - } - if (getG2().containsVertex(vertex)) { - res.addAll(getG2().incomingEdgesOf(vertex)); - } - return Collections.unmodifiableSet(res); - } - - public int outDegreeOf(V vertex) - { - Set res = outgoingEdgesOf(vertex); - return res.size(); - } - - public Set outgoingEdgesOf(V vertex) - { - Set res = new HashSet(); - if (getG1().containsVertex(vertex)) { - res.addAll(getG1().outgoingEdgesOf(vertex)); - } - if (getG2().containsVertex(vertex)) { - res.addAll(getG2().outgoingEdgesOf(vertex)); - } - return Collections.unmodifiableSet(res); - } -} - -// End DirectedGraphUnion.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMaskSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMaskSubgraph.java deleted file mode 100644 index fdeaf323789..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMaskSubgraph.java +++ /dev/null @@ -1,64 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * DirectedMaskSubgraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * A directed graph that is a {@link MaskSubgraph} on another graph. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class DirectedMaskSubgraph - extends MaskSubgraph - implements DirectedGraph -{ - //~ Constructors ----------------------------------------------------------- - - public DirectedMaskSubgraph( - DirectedGraph base, - MaskFunctor mask) - { - super(base, mask); - } -} - -// End DirectedMaskSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMultigraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMultigraph.java index 869674fb4e0..03ccf4cb44b 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMultigraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMultigraph.java @@ -1,82 +1,92 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------- - * DirectedMultigraph.java - * ----------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A directed multigraph. A directed multigraph is a non-simple directed graph - * in which loops and multiple edges between any two vertices are permitted. + * 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 - implements DirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = 2919338637676573948L; - private static final long serialVersionUID = 3258408413590599219L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public DirectedMultigraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * Creates a new directed multigraph. - * + * 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 DirectedMultigraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new DirectedMultigraph<>(edgeClass)); } /** - * Creates a new directed multigraph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 DirectedMultigraph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef, true, true); + return new GraphBuilder<>(new DirectedMultigraph<>(null, edgeSupplier, false)); } -} -// End DirectedMultigraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedPseudograph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedPseudograph.java index 6cf0be5907b..32cd03a68ad 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedPseudograph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedPseudograph.java @@ -1,80 +1,94 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (barak_naveh@users.sourceforge.net) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2004-2023, by Christian Hammer and Contributors. * - * 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. - */ -/* ---------------- - * DirectedPseudograph.java - * ---------------- - * (C) Copyright 2004-2008, by Christian Hammer and Contributors. - * - * Original Author: Christian Hammer - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 11-Mar-2004 : Initial revision: generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 edges are permitted. If you're unsure - * about pseudographs, see: + * 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 - implements DirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -7461248851245878913L; - private static final long serialVersionUID = -8300409752893486415L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public DirectedPseudograph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * @see AbstractBaseGraph + * 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 DirectedPseudograph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new DirectedPseudograph<>(edgeClass)); } /** - * @see AbstractBaseGraph + * 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 DirectedPseudograph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef, true, true); + return new GraphBuilder<>(new DirectedPseudograph<>(null, edgeSupplier, false)); } } - -// End DirectedPseudograph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedSubgraph.java deleted file mode 100644 index 51611352568..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedSubgraph.java +++ /dev/null @@ -1,158 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------- - * DirectedSubgraph.java - * --------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -/** - * A directed graph that is a subgraph on other graph. - * - * @see Subgraph - */ -public class DirectedSubgraph - extends Subgraph> - implements DirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3616445700507054133L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new directed 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 DirectedSubgraph( - DirectedGraph base, - Set vertexSubset, - Set edgeSubset) - { - super(base, vertexSubset, edgeSubset); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see DirectedGraph#inDegreeOf(Object) - */ - public int inDegreeOf(V vertex) - { - assertVertexExist(vertex); - - int degree = 0; - - for (E e : getBase().incomingEdgesOf(vertex)) { - if (containsEdge(e)) { - degree++; - } - } - - return degree; - } - - /** - * @see DirectedGraph#incomingEdgesOf(Object) - */ - public Set incomingEdgesOf(V vertex) - { - assertVertexExist(vertex); - - Set edges = new ArrayUnenforcedSet(); - - for (E e : getBase().incomingEdgesOf(vertex)) { - if (containsEdge(e)) { - edges.add(e); - } - } - - return edges; - } - - /** - * @see DirectedGraph#outDegreeOf(Object) - */ - public int outDegreeOf(V vertex) - { - assertVertexExist(vertex); - - int degree = 0; - - for (E e : getBase().outgoingEdgesOf(vertex)) { - if (containsEdge(e)) { - degree++; - } - } - - return degree; - } - - /** - * @see DirectedGraph#outgoingEdgesOf(Object) - */ - public Set outgoingEdgesOf(V vertex) - { - assertVertexExist(vertex); - - Set edges = new ArrayUnenforcedSet(); - - for (E e : getBase().outgoingEdgesOf(vertex)) { - if (containsEdge(e)) { - edges.add(e); - } - } - - return edges; - } -} - -// End DirectedSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedMultigraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedMultigraph.java index 06484ab38d8..ae3bc7d7106 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedMultigraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedMultigraph.java @@ -1,84 +1,89 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------- - * DirectedWeightedMultigraph.java - * ------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Jun-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A directed weighted multigraph. A directed weighted multigraph is a - * non-simple directed graph in which loops and multiple edges between any two - * vertices are permitted, and edges have weights. + * 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 - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = 1984381120642160572L; - private static final long serialVersionUID = 4049071636005206066L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public DirectedWeightedMultigraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass)); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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); + } /** - * Creates a new directed weighted multigraph. - * + * 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 DirectedWeightedMultigraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new DirectedWeightedMultigraph<>(edgeClass)); } /** - * Creates a new directed weighted multigraph with the specified edge - * factory. - * - * @param ef the edge factory of the new graph. + * 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 DirectedWeightedMultigraph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef); + return new GraphBuilder<>(new DirectedWeightedMultigraph<>(null, edgeSupplier)); } -} -// End DirectedWeightedMultigraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedPseudograph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedPseudograph.java new file mode 100644 index 00000000000..75f33b415bb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedPseudograph.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2003-2023, 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedSubgraph.java deleted file mode 100644 index 8e852d5baba..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedSubgraph.java +++ /dev/null @@ -1,83 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------------- - * DirectedWeightedSubgraph.java - * ----------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A directed weighted graph that is a subgraph on other graph. - * - * @see Subgraph - */ -public class DirectedWeightedSubgraph - extends DirectedSubgraph - implements WeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3905799799168250680L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new weighted directed 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 DirectedWeightedSubgraph( - WeightedGraph base, - Set vertexSubset, - Set edgeSubset) - { - super((DirectedGraph) base, vertexSubset, edgeSubset); - } -} - -// End DirectedWeightedSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeReversedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeReversedGraph.java index ab21b56ab6c..9c4eeb706aa 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeReversedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeReversedGraph.java @@ -1,92 +1,63 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2006-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------- - * EdgeReversedGraph.java - * ------------- - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 16-Sept-2006 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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. + * 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}. * - *

    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 DirectedGraph + implements Graph { - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = 9091361782455418631L; - - //~ Constructors ----------------------------------------------------------- + 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. + * @param g the base (backing) graph on which the edge-reversed view will be based. */ - public EdgeReversedGraph(DirectedGraph g) + public EdgeReversedGraph(Graph g) { super(g); } - //~ Methods ---------------------------------------------------------------- - /** * @see Graph#getEdge(Object, Object) */ + @Override public E getEdge(V sourceVertex, V targetVertex) { return super.getEdge(targetVertex, sourceVertex); @@ -95,6 +66,7 @@ public E getEdge(V sourceVertex, V targetVertex) /** * @see Graph#getAllEdges(Object, Object) */ + @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return super.getAllEdges(targetVertex, sourceVertex); @@ -103,6 +75,7 @@ public Set getAllEdges(V sourceVertex, V targetVertex) /** * @see Graph#addEdge(Object, Object) */ + @Override public E addEdge(V sourceVertex, V targetVertex) { return super.addEdge(targetVertex, sourceVertex); @@ -111,38 +84,43 @@ public E addEdge(V sourceVertex, V targetVertex) /** * @see Graph#addEdge(Object, Object, Object) */ + @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { return super.addEdge(targetVertex, sourceVertex, e); } /** - * @see DirectedGraph#inDegreeOf(Object) + * @see Graph#inDegreeOf(Object) */ + @Override public int inDegreeOf(V vertex) { return super.outDegreeOf(vertex); } /** - * @see DirectedGraph#outDegreeOf(Object) + * @see Graph#outDegreeOf(Object) */ + @Override public int outDegreeOf(V vertex) { return super.inDegreeOf(vertex); } /** - * @see DirectedGraph#incomingEdgesOf(Object) + * @see Graph#incomingEdgesOf(Object) */ + @Override public Set incomingEdgesOf(V vertex) { return super.outgoingEdgesOf(vertex); } /** - * @see DirectedGraph#outgoingEdgesOf(Object) + * @see Graph#outgoingEdgesOf(Object) */ + @Override public Set outgoingEdgesOf(V vertex) { return super.incomingEdgesOf(vertex); @@ -151,6 +129,7 @@ public Set outgoingEdgesOf(V vertex) /** * @see Graph#removeEdge(Object, Object) */ + @Override public E removeEdge(V sourceVertex, V targetVertex) { return super.removeEdge(targetVertex, sourceVertex); @@ -159,6 +138,7 @@ public E removeEdge(V sourceVertex, V targetVertex) /** * @see Graph#getEdgeSource(Object) */ + @Override public V getEdgeSource(E e) { return super.getEdgeTarget(e); @@ -167,6 +147,7 @@ public V getEdgeSource(E e) /** * @see Graph#getEdgeTarget(Object) */ + @Override public V getEdgeTarget(E e) { return super.getEdgeSource(e); @@ -175,13 +156,9 @@ public V getEdgeTarget(E e) /** * @see java.lang.Object#toString() */ + @Override public String toString() { - return toStringFromSets( - vertexSet(), - edgeSet(), - true); + return toStringFromSets(vertexSet(), edgeSet(), getType().isDirected()); } } - -// End EdgeReversedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeSetFactory.java b/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeSetFactory.java index 828659ce866..ac44c66a011 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeSetFactory.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeSetFactory.java @@ -1,73 +1,45 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * EdgeSetFactory.java - * ---------------- - * (C) Copyright 2005-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2005-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 01-Jun-2005 : Initial revision (JVS); - * 06-Aug-2005 : Made generic (CH); - * 07-May-2006 : Renamed and changed from List to Set (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 { - //~ Methods ---------------------------------------------------------------- - /** * 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) + * @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 */ - public Set createEdgeSet(V vertex); + Set createEdgeSet(V vertex); } - -// End EdgeSetFactory.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/FastLookupGraphSpecificsStrategy.java b/jgrapht-core/src/main/java/org/jgrapht/graph/FastLookupGraphSpecificsStrategy.java new file mode 100644 index 00000000000..9c6fcddd47f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/FastLookupGraphSpecificsStrategy.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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()); + } + }; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphCycleProhibitedException.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphCycleProhibitedException.java new file mode 100644 index 00000000000..96f71a03c80 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphCycleProhibitedException.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2021-2023, by Magnus Gunnarsson and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 indicating that the vertexes supplied to {@link DirectedAcyclicGraph} would cause a + * cycle. + * + * @author EnderCrypt (Magnus Gunnarsson) + */ +public class GraphCycleProhibitedException + extends IllegalArgumentException +{ + private static final long serialVersionUID = 2440845437318796595L; + + public GraphCycleProhibitedException() + { + super("Edge would induce a cycle"); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphDelegator.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphDelegator.java index 1f4e6dc018e..e0dcb758d9f 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphDelegator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphDelegator.java @@ -1,298 +1,379 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ------------------- - * GraphDelegator.java - * ------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 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. + * 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 graph does not pass the hashCode and equals operations through to the backing graph, + * but relies on {@code Object}'s {@code equals} and {@code hashCode} methods. + *

    * - *

    This class is mostly used as a base for extending subclasses.

    + *

    + * 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 - * @since Jul 20, 2003 */ public class GraphDelegator extends AbstractGraph - implements Graph, - Serializable + implements Graph, Serializable { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3257005445226181425L; + private static final long serialVersionUID = -215068279981825448L; - //~ Instance fields -------------------------------------------------------- - - /** + /* * The graph to which operations are delegated. */ - private Graph delegate; - - //~ Constructors ----------------------------------------------------------- + private final Graph delegate; + private final Supplier vertexSupplier; + private final Supplier edgeSupplier; /** - * Constructor for GraphDelegator. - * - * @param g the backing graph (the delegate). + * Constructs a new {@code GraphDelegator}. * - * @throws IllegalArgumentException iff g==null + * @param graph the backing graph (the delegate). + * + * @throws NullPointerException if argument is {@code null} + */ + public GraphDelegator(Graph graph) + { + this(graph, null, null); + } + + /** + * Constructs a new {@code GraphDelegator}. + * + * @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. + * + * @throws NullPointerException if {@code graph} is {@code null} */ - public GraphDelegator(Graph g) + 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; + } - if (g == null) { - throw new IllegalArgumentException("g must not be null."); + /** + * @return the vertex supplier of this delegator or the backing graph's + * vertex supplier if this delegator does not have a vertex supplier + */ + @Override + public Supplier getVertexSupplier() + { + if (vertexSupplier != null) { + return vertexSupplier; + } else { + return delegate.getVertexSupplier(); } - - delegate = g; } - //~ Methods ---------------------------------------------------------------- + /** + * @return the edge supplier of this delegator or the backing graph's + * edge supplier if this delegator does not have an edge supplier + */ + @Override + public Supplier getEdgeSupplier() + { + if (edgeSupplier != null) { + return edgeSupplier; + } else { + return delegate.getEdgeSupplier(); + } + } /** - * @see Graph#getAllEdges(Object, Object) + * {@inheritDoc} */ + @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return delegate.getAllEdges(sourceVertex, targetVertex); } /** - * @see Graph#getEdge(Object, Object) + * {@inheritDoc} */ + @Override public E getEdge(V sourceVertex, V targetVertex) { return delegate.getEdge(sourceVertex, targetVertex); } /** - * @see Graph#getEdgeFactory() + * @throws ClassCastException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ - public EdgeFactory getEdgeFactory() + @Override + public E addEdge(V sourceVertex, V targetVertex) { - return delegate.getEdgeFactory(); + /* + * 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); } /** - * @see Graph#addEdge(Object, Object) + * @throws ClassCastException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ - public E addEdge(V sourceVertex, V targetVertex) + @Override + public boolean addEdge(V sourceVertex, V targetVertex, E e) { - return delegate.addEdge(sourceVertex, targetVertex); + return delegate.addEdge(sourceVertex, targetVertex, e); } /** - * @see Graph#addEdge(Object, Object, Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ - public boolean addEdge(V sourceVertex, V targetVertex, E e) + @Override + public V addVertex() { - return delegate.addEdge(sourceVertex, targetVertex, e); + /* + * Use our own vertex supplier, if provided. + */ + if (vertexSupplier != null) { + V v = vertexSupplier.get(); + return this.addVertex(v) ? v : null; + } + return delegate.addVertex(); } /** - * @see Graph#addVertex(Object) + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public boolean addVertex(V v) { return delegate.addVertex(v); } /** - * @see Graph#containsEdge(Object) + * {@inheritDoc} */ + @Override public boolean containsEdge(E e) { return delegate.containsEdge(e); } /** - * @see Graph#containsVertex(Object) + * {@inheritDoc} */ + @Override public boolean containsVertex(V v) { return delegate.containsVertex(v); } /** - * @see UndirectedGraph#degreeOf(Object) + * 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 ((UndirectedGraph) delegate).degreeOf(vertex); + return delegate.degreeOf(vertex); } /** - * @see Graph#edgeSet() + * {@inheritDoc} */ + @Override public Set edgeSet() { return delegate.edgeSet(); } /** - * @see Graph#edgesOf(Object) + * {@inheritDoc} */ + @Override public Set edgesOf(V vertex) { return delegate.edgesOf(vertex); } /** - * @see DirectedGraph#inDegreeOf(Object) + * {@inheritDoc} */ + @Override public int inDegreeOf(V vertex) { - return ((DirectedGraph) delegate).inDegreeOf(vertex); + return delegate.inDegreeOf(vertex); } /** - * @see DirectedGraph#incomingEdgesOf(Object) + * {@inheritDoc} */ + @Override public Set incomingEdgesOf(V vertex) { - return ((DirectedGraph) delegate).incomingEdgesOf(vertex); + return delegate.incomingEdgesOf(vertex); } /** - * @see DirectedGraph#outDegreeOf(Object) + * {@inheritDoc} */ + @Override public int outDegreeOf(V vertex) { - return ((DirectedGraph) delegate).outDegreeOf(vertex); + return delegate.outDegreeOf(vertex); } /** - * @see DirectedGraph#outgoingEdgesOf(Object) + * {@inheritDoc} */ + @Override public Set outgoingEdgesOf(V vertex) { - return ((DirectedGraph) delegate).outgoingEdgesOf(vertex); + return delegate.outgoingEdgesOf(vertex); } /** - * @see Graph#removeEdge(Object) + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public boolean removeEdge(E e) { return delegate.removeEdge(e); } /** - * @see Graph#removeEdge(Object, Object) + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public E removeEdge(V sourceVertex, V targetVertex) { return delegate.removeEdge(sourceVertex, targetVertex); } /** - * @see Graph#removeVertex(Object) + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public boolean removeVertex(V v) { return delegate.removeVertex(v); } /** - * @see java.lang.Object#toString() + * {@inheritDoc} */ + @Override public String toString() { return delegate.toString(); } /** - * @see Graph#vertexSet() + * {@inheritDoc} */ + @Override public Set vertexSet() { return delegate.vertexSet(); } /** - * @see Graph#getEdgeSource(Object) + * {@inheritDoc} */ + @Override public V getEdgeSource(E e) { return delegate.getEdgeSource(e); } /** - * @see Graph#getEdgeTarget(Object) + * {@inheritDoc} */ + @Override public V getEdgeTarget(E e) { return delegate.getEdgeTarget(e); } /** - * @see Graph#getEdgeWeight(Object) + * @throws NullPointerException {@inheritDoc} */ + @Override public double getEdgeWeight(E e) { return delegate.getEdgeWeight(e); } /** - * @see WeightedGraph#setEdgeWeight(Object, double) + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public void setEdgeWeight(E e, double weight) { - ((WeightedGraph) delegate).setEdgeWeight(e, weight); + delegate.setEdgeWeight(e, weight); + } + + /** + * {@inheritDoc} + */ + @Override + public GraphType getType() + { + return delegate.getType(); } -} -// End GraphDelegator.java + /** + * Return the backing graph (the delegate). + * + * @return the backing graph (the delegate) + */ + protected Graph getDelegate() + { + return delegate; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphPathImpl.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphPathImpl.java deleted file mode 100644 index b2e3b911282..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphPathImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * GraphPathImpl.java - * ---------------- - * (C) Copyright 2009-2009, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 03-Jul-2009 : Initial revision (JVS); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * GraphPathImpl is a default implementation of {@link GraphPath}. - * - * @author John Sichi - * @version $Id$ - */ -public class GraphPathImpl - implements GraphPath -{ - //~ Instance fields -------------------------------------------------------- - - private Graph graph; - - private List edgeList; - - private V startVertex; - - private V endVertex; - - private double weight; - - //~ Constructors ----------------------------------------------------------- - - public GraphPathImpl( - Graph graph, - V startVertex, - V endVertex, - List edgeList, - double weight) - { - this.graph = graph; - this.startVertex = startVertex; - this.endVertex = endVertex; - this.edgeList = edgeList; - this.weight = weight; - } - - //~ Methods ---------------------------------------------------------------- - - // implement GraphPath - public Graph getGraph() - { - return graph; - } - - // implement GraphPath - public V getStartVertex() - { - return startVertex; - } - - // implement GraphPath - public V getEndVertex() - { - return endVertex; - } - - // implement GraphPath - public List getEdgeList() - { - return edgeList; - } - - // implement GraphPath - public double getWeight() - { - return weight; - } - - // override Object - public String toString() - { - return edgeList.toString(); - } -} - -// End GraphPathImpl.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphSpecificsStrategy.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphSpecificsStrategy.java new file mode 100644 index 00000000000..2ce23628ae7 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphSpecificsStrategy.java @@ -0,0 +1,72 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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<>(); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphUnion.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphUnion.java deleted file mode 100644 index 6659c078046..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphUnion.java +++ /dev/null @@ -1,288 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * GraphUnion.java - * ------------------------- - * (C) Copyright 2009-2009, by Ilya Razenshteyn - * - * Original Author: Ilya Razenshteyn and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 02-Feb-2009 : Initial revision (IR); - * - */ -package org.jgrapht.graph; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -/** - *

    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 E1.

    - * - *

    GraphUnion implements Graph interface. - * GraphUnion uses WeightCombiner to choose policy for calculating - * edge weight.

    - */ -public class GraphUnion> - extends AbstractGraph - implements Serializable -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = -740199233080172450L; - - private static final String READ_ONLY = "union of graphs is read-only"; - - //~ Instance fields -------------------------------------------------------- - - private G g1; - private G g2; - private WeightCombiner operator; - - //~ Constructors ----------------------------------------------------------- - - public GraphUnion(G g1, G g2, WeightCombiner operator) - { - if (g1 == null) { - throw new NullPointerException("g1 is null"); - } - if (g2 == null) { - throw new NullPointerException("g2 is null"); - } - if (g1 == g2) { - throw new IllegalArgumentException("g1 is equal to g2"); - } - this.g1 = g1; - this.g2 = g2; - this.operator = operator; - } - - public GraphUnion(G g1, G g2) - { - this(g1, g2, WeightCombiner.SUM); - } - - //~ Methods ---------------------------------------------------------------- - - public Set getAllEdges(V sourceVertex, V targetVertex) - { - Set res = new HashSet(); - if (g1.containsVertex(sourceVertex) - && g1.containsVertex(targetVertex)) - { - res.addAll(g1.getAllEdges(sourceVertex, targetVertex)); - } - if (g2.containsVertex(sourceVertex) - && g2.containsVertex(targetVertex)) - { - res.addAll(g2.getAllEdges(sourceVertex, targetVertex)); - } - return Collections.unmodifiableSet(res); - } - - 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; - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public EdgeFactory getEdgeFactory() - { - throw new UnsupportedOperationException(READ_ONLY); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public E addEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public boolean addEdge(V sourceVertex, V targetVertex, E e) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public boolean addVertex(V v) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - public boolean containsEdge(E e) - { - return g1.containsEdge(e) || g2.containsEdge(e); - } - - public boolean containsVertex(V v) - { - return g1.containsVertex(v) || g2.containsVertex(v); - } - - public Set edgeSet() - { - Set res = new HashSet(); - res.addAll(g1.edgeSet()); - res.addAll(g2.edgeSet()); - return Collections.unmodifiableSet(res); - } - - public Set edgesOf(V vertex) - { - Set res = new HashSet(); - if (g1.containsVertex(vertex)) { - res.addAll(g1.edgesOf(vertex)); - } - if (g2.containsVertex(vertex)) { - res.addAll(g2.edgesOf(vertex)); - } - return Collections.unmodifiableSet(res); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public E removeEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public boolean removeEdge(E e) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - /** - * Throws UnsupportedOperationException, because - * GraphUnion is read-only. - */ - public boolean removeVertex(V v) - { - throw new UnsupportedOperationException(READ_ONLY); - } - - public Set vertexSet() - { - Set res = new HashSet(); - res.addAll(g1.vertexSet()); - res.addAll(g2.vertexSet()); - return Collections.unmodifiableSet(res); - } - - public V getEdgeSource(E e) - { - if (g1.containsEdge(e)) { - return g1.getEdgeSource(e); - } - if (g2.containsEdge(e)) { - return g2.getEdgeSource(e); - } - return null; - } - - public V getEdgeTarget(E e) - { - if (g1.containsEdge(e)) { - return g1.getEdgeTarget(e); - } - if (g2.containsEdge(e)) { - return g2.getEdgeTarget(e); - } - return null; - } - - 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"); - } - - /** - * @return G1 - */ - public G getG1() - { - return g1; - } - - /** - * @return G2 - */ - public G getG2() - { - return g2; - } -} - -// End GraphUnion.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/GraphWalk.java b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphWalk.java new file mode 100644 index 00000000000..78c82ab8c48 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/GraphWalk.java @@ -0,0 +1,527 @@ +/* + * (C) Copyright 2016-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdge.java b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdge.java index bc7aa36f6f4..203ad666503 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdge.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdge.java @@ -1,73 +1,43 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * IntrusiveEdge.java - * ------------------- - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2006-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 28-May-2006 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * 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 + implements Cloneable, Serializable { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3258408452177932855L; - //~ Instance fields -------------------------------------------------------- - Object source; Object target; - //~ Methods ---------------------------------------------------------------- - /** * @see Object#clone() */ + @Override public Object clone() { try { @@ -78,5 +48,3 @@ public Object clone() } } } - -// End IntrusiveEdge.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgeException.java b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgeException.java new file mode 100644 index 00000000000..f250bf669e2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgeException.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2021-2023, 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 + ">"); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgesSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgesSpecifics.java new file mode 100644 index 00000000000..094bad9f1bd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgesSpecifics.java @@ -0,0 +1,101 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveWeightedEdge.java b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveWeightedEdge.java new file mode 100644 index 00000000000..ce34c3951a3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveWeightedEdge.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/InvalidGraphWalkException.java b/jgrapht-core/src/main/java/org/jgrapht/graph/InvalidGraphWalkException.java new file mode 100644 index 00000000000..6eef1365bff --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/InvalidGraphWalkException.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2017-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedGraph.java deleted file mode 100644 index 7b9644914a3..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedGraph.java +++ /dev/null @@ -1,83 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------------- - * ListenableDirectedGraph.java - * ---------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * A directed graph which is also {@link org.jgrapht.ListenableGraph}. - * - * @see DefaultListenableGraph - */ -public class ListenableDirectedGraph - extends DefaultListenableGraph - implements DirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3257571698126368824L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new listenable directed graph. - * - * @param edgeClass class on which to base factory for edges - */ - public ListenableDirectedGraph(Class edgeClass) - { - this(new DefaultDirectedGraph(edgeClass)); - } - - /** - * Creates a new listenable directed graph. - * - * @param base the backing graph. - */ - public ListenableDirectedGraph(DirectedGraph base) - { - super(base); - } -} - -// End ListenableDirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedWeightedGraph.java deleted file mode 100644 index eaff740e74c..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableDirectedWeightedGraph.java +++ /dev/null @@ -1,84 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------------ - * ListenableDirectedWeightedGraph.java - * ------------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id: ListenableDirectedWeightedGraph.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Jun-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * A directed weighted graph which is also {@link org.jgrapht.ListenableGraph}. - * - * @see DefaultListenableGraph - */ -public class ListenableDirectedWeightedGraph - extends ListenableDirectedGraph - implements WeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3977582476627621938L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new listenable directed weighted graph. - * - * @param edgeClass class on which to base factory for edges - */ - public ListenableDirectedWeightedGraph(Class edgeClass) - { - this(new DefaultDirectedWeightedGraph(edgeClass)); - } - - /** - * Creates a new listenable directed weighted graph. - * - * @param base the backing graph. - */ - public ListenableDirectedWeightedGraph(WeightedGraph base) - { - super((DirectedGraph) base); - } -} - -// End ListenableDirectedWeightedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedGraph.java deleted file mode 100644 index 3f44b5074fb..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedGraph.java +++ /dev/null @@ -1,83 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * ListenableUndirectedGraph.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * An undirected graph which is also {@link org.jgrapht.ListenableGraph}. - * - * @see DefaultListenableGraph - */ -public class ListenableUndirectedGraph - extends DefaultListenableGraph - implements UndirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3256999969193145905L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new listenable undirected simple graph. - * - * @param edgeClass class on which to base factory for edges - */ - public ListenableUndirectedGraph(Class edgeClass) - { - this(new SimpleGraph(edgeClass)); - } - - /** - * Creates a new listenable undirected graph. - * - * @param base the backing graph. - */ - public ListenableUndirectedGraph(UndirectedGraph base) - { - super(base); - } -} - -// End ListenableUndirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedWeightedGraph.java deleted file mode 100644 index 1910c50c8b0..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ListenableUndirectedWeightedGraph.java +++ /dev/null @@ -1,85 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------------------- - * ListenableUndirectedWeightedGraph.java - * -------------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id: ListenableUndirectedWeightedGraph.java 485 2006-06-26 09:12:14Z - * perfecthash $ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Jun-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * An undirected weighted graph which is also {@link - * org.jgrapht.ListenableGraph}. - * - * @see DefaultListenableGraph - */ -public class ListenableUndirectedWeightedGraph - extends ListenableUndirectedGraph - implements WeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3690762799613949747L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new listenable undirected weighted graph. - * - * @param edgeClass class on which to base factory for edges - */ - public ListenableUndirectedWeightedGraph(Class edgeClass) - { - this(new SimpleWeightedGraph(edgeClass)); - } - - /** - * Creates a new listenable undirected weighted graph. - * - * @param base the backing graph. - */ - public ListenableUndirectedWeightedGraph(WeightedGraph base) - { - super((UndirectedGraph) base); - } -} - -// End ListenableUndirectedWeightedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskEdgeSet.java b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskEdgeSet.java index 234ad37ea15..da71dce66bb 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskEdgeSet.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskEdgeSet.java @@ -1,149 +1,102 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2007-2023, by France Telecom and Contributors. * - * 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. - */ -/* ------------------------- - * MaskEdgeSet.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.util.*; -import org.jgrapht.util.PrefetchIterator.*; +import java.io.*; +import java.util.*; +import java.util.function.*; /** * Helper for {@link MaskSubgraph}. * - * @author Guillaume Boulmier - * @since July 5, 2007 */ class MaskEdgeSet extends AbstractSet + implements Serializable { - //~ Instance fields -------------------------------------------------------- - - private Set edgeSet; - - private Graph graph; - - private MaskFunctor mask; - - private transient TypeUtil edgeTypeDecl = null; + private static final long serialVersionUID = 4208908842850100708L; - private int size; - - //~ Constructors ----------------------------------------------------------- + private final Graph graph; + private final Set edgeSet; + private final Predicate vertexMask; + private final Predicate edgeMask; public MaskEdgeSet( - Graph graph, - Set edgeSet, - MaskFunctor mask) + Graph graph, Set edgeSet, Predicate vertexMask, Predicate edgeMask) { this.graph = graph; this.edgeSet = edgeSet; - this.mask = mask; - this.size = -1; + this.vertexMask = vertexMask; + this.edgeMask = edgeMask; } - //~ Methods ---------------------------------------------------------------- - /** - * @see java.util.Collection#contains(java.lang.Object) + * {@inheritDoc} */ + @Override public boolean contains(Object o) { - return this.edgeSet.contains(o) - && !this.mask.isEdgeMasked(TypeUtil.uncheckedCast(o, edgeTypeDecl)); + 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)); } /** - * @see java.util.Set#iterator() + * {@inheritDoc} */ + @Override public Iterator iterator() { - return new PrefetchIterator(new MaskEdgeSetNextElementFunctor()); + return edgeSet + .stream() + .filter( + e -> !edgeMask.test(e) && !vertexMask.test(graph.getEdgeSource(e)) + && !vertexMask.test(graph.getEdgeTarget(e))) + .iterator(); } /** - * @see java.util.Set#size() + * {@inheritDoc} */ + @Override public int size() { - if (this.size == -1) { - this.size = 0; - for (Iterator iter = iterator(); iter.hasNext();) { - iter.next(); - this.size++; - } - } - return this.size; + return (int) edgeSet + .stream() + .filter( + e -> !edgeMask.test(e) && !vertexMask.test(graph.getEdgeSource(e)) + && !vertexMask.test(graph.getEdgeTarget(e))) + .count(); } - //~ Inner Classes ---------------------------------------------------------- - - private class MaskEdgeSetNextElementFunctor - implements NextElementFunctor + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { - private Iterator iter; - - public MaskEdgeSetNextElementFunctor() - { - this.iter = MaskEdgeSet.this.edgeSet.iterator(); - } - - public E nextElement() - throws NoSuchElementException - { - E edge = this.iter.next(); - while (isMasked(edge)) { - edge = this.iter.next(); - } - return edge; - } - - private boolean isMasked(E edge) - { - return MaskEdgeSet.this.mask.isEdgeMasked(edge) - || MaskEdgeSet.this.mask.isVertexMasked( - MaskEdgeSet.this.graph.getEdgeSource(edge)) - || MaskEdgeSet.this.mask.isVertexMasked( - MaskEdgeSet.this.graph.getEdgeTarget(edge)); - } + return !iterator().hasNext(); } } - -// End MaskEdgeSet.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskFunctor.java b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskFunctor.java deleted file mode 100644 index a16547ae45d..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskFunctor.java +++ /dev/null @@ -1,72 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * MaskFunctor.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.graph; - -/** - * A functor interface for masking out vertices and edges of a graph. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public interface MaskFunctor -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Returns true if the edge is masked, false - * otherwise. - * - * @param edge edge. - * - * @return . - */ - public boolean isEdgeMasked(E edge); - - /** - * Returns true if the vertex is masked, false - * otherwise. - * - * @param vertex vertex. - * - * @return . - */ - public boolean isVertexMasked(V vertex); -} - -// End MaskFunctor.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskSubgraph.java index 18951f57422..e74c4840472 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskSubgraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskSubgraph.java @@ -1,298 +1,382 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2007-2023, by France Telecom and Contributors. * - * 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. - */ -/* ------------------------- - * MaskSubgraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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). - * - * @author Guillaume Boulmier - * @since July 5, 2007 + * 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 { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -7397441126669119179L; private static final String UNMODIFIABLE = "this graph is unmodifiable"; - //~ Instance fields -------------------------------------------------------- - - private Graph base; - - private Set edges; - - private MaskFunctor mask; - - private Set vertices; - - //~ Constructors ----------------------------------------------------------- + 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 mask vertices and edges to exclude in the subgraph. If a - * vertex/edge is masked, it is as if it is not in the subgraph. + * @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, MaskFunctor mask) + public MaskSubgraph(Graph base, Predicate vertexMask, Predicate edgeMask) { super(); - this.base = base; - this.mask = mask; - - this.vertices = new MaskVertexSet(base.vertexSet(), mask); - this.edges = new MaskEdgeSet(base, base.edgeSet(), mask); + 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); } - //~ Methods ---------------------------------------------------------------- - /** - * @see Graph#addEdge(Object, Object) + * {@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); } /** - * @see Graph#addVertex(Object) + * {@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 !this.mask.isVertexMasked(v) && this.base.containsVertex(v); + return vertexSet().contains(v); } /** - * @see UndirectedGraph#degreeOf(Object) + * {@inheritDoc} */ - public int degreeOf(V vertex) - { - return edgesOf(vertex).size(); - } - + @Override public Set edgeSet() { - return this.edges; + return edges; } + /** + * {@inheritDoc} + */ + @Override public Set edgesOf(V vertex) { assertVertexExist(vertex); - return new MaskEdgeSet( - this.base, - this.base.edgesOf(vertex), - this.mask); + return new MaskEdgeSet<>(base, base.edgesOf(vertex), vertexMask, edgeMask); } - public Set getAllEdges(V sourceVertex, V targetVertex) + /** + * {@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) { - Set edges = null; - - if (containsVertex(sourceVertex) && containsVertex(targetVertex)) { - return new MaskEdgeSet( - this.base, - this.base.getAllEdges( - sourceVertex, - targetVertex), - this.mask); + 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; } + } - return edges; + /** + * {@inheritDoc} + */ + @Override + public Set incomingEdgesOf(V vertex) + { + assertVertexExist(vertex); + + return new MaskEdgeSet<>(base, base.incomingEdgesOf(vertex), vertexMask, edgeMask); } - public E getEdge(V sourceVertex, V targetVertex) + /** + * {@inheritDoc} + */ + @Override + public int inDegreeOf(V vertex) { - Set edges = getAllEdges(sourceVertex, targetVertex); + if (baseType.isUndirected()) { + return degreeOf(vertex); + } else { + return incomingEdgesOf(vertex).size(); + } + } - if ((edges == null) || edges.isEmpty()) { - return null; + /** + * {@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 edges.iterator().next(); + return outgoingEdgesOf(vertex).size(); } } - public EdgeFactory getEdgeFactory() + /** + * {@inheritDoc} + */ + @Override + public Set getAllEdges(V sourceVertex, V targetVertex) { - return this.base.getEdgeFactory(); + if (containsVertex(sourceVertex) && containsVertex(targetVertex)) { + return new MaskEdgeSet<>( + base, base.getAllEdges(sourceVertex, targetVertex), vertexMask, edgeMask); + } else + return null; } - public V getEdgeSource(E edge) + /** + * {@inheritDoc} + */ + @Override + public E getEdge(V sourceVertex, V targetVertex) { - assert (edgeSet().contains(edge)); + Set edges = getAllEdges(sourceVertex, targetVertex); - return this.base.getEdgeSource(edge); + if (edges == null) { + return null; + } else { + return edges.stream().findAny().orElse(null); + } } - public V getEdgeTarget(E edge) + /** + * {@inheritDoc} + */ + @Override + public Supplier getVertexSupplier() { - assert (edgeSet().contains(edge)); + return base.getVertexSupplier(); + } - return this.base.getEdgeTarget(edge); + /** + * {@inheritDoc} + */ + @Override + public Supplier getEdgeSupplier() + { + return base.getEdgeSupplier(); } - public double getEdgeWeight(E edge) + /** + * {@inheritDoc} + */ + @Override + public V getEdgeSource(E edge) { assert (edgeSet().contains(edge)); - return this.base.getEdgeWeight(edge); + return base.getEdgeSource(edge); } /** - * @see DirectedGraph#incomingEdgesOf(Object) + * {@inheritDoc} */ - public Set incomingEdgesOf(V vertex) + @Override + public V getEdgeTarget(E edge) { - assertVertexExist(vertex); + assert (edgeSet().contains(edge)); - return new MaskEdgeSet( - this.base, - ((DirectedGraph) this.base).incomingEdgesOf(vertex), - this.mask); + return base.getEdgeTarget(edge); } /** - * @see DirectedGraph#inDegreeOf(Object) + * {@inheritDoc} */ - public int inDegreeOf(V vertex) + @Override + public GraphType getType() { - return incomingEdgesOf(vertex).size(); + return baseType.asUnmodifiable(); } /** - * @see DirectedGraph#outDegreeOf(Object) + * {@inheritDoc} */ - public int outDegreeOf(V vertex) + @Override + public double getEdgeWeight(E edge) { - return outgoingEdgesOf(vertex).size(); + assert (edgeSet().contains(edge)); + + return base.getEdgeWeight(edge); } /** - * @see DirectedGraph#outgoingEdgesOf(Object) + * {@inheritDoc} */ - public Set outgoingEdgesOf(V vertex) + @Override + public void setEdgeWeight(E edge, double weight) { - assertVertexExist(vertex); + assert (edgeSet().contains(edge)); - return new MaskEdgeSet( - this.base, - ((DirectedGraph) this.base).outgoingEdgesOf(vertex), - this.mask); + base.setEdgeWeight(edge, weight); } /** - * @see Graph#removeAllEdges(Collection) + * {@inheritDoc} */ + @Override public boolean removeAllEdges(Collection edges) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** - * @see Graph#removeAllEdges(Object, Object) + * {@inheritDoc} */ + @Override public Set removeAllEdges(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** - * @see Graph#removeAllVertices(Collection) + * {@inheritDoc} */ + @Override public boolean removeAllVertices(Collection vertices) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** - * @see Graph#removeEdge(Object) + * {@inheritDoc} */ + @Override public boolean removeEdge(E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** - * @see Graph#removeEdge(Object, Object) + * {@inheritDoc} */ + @Override public E removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** - * @see Graph#removeVertex(Object) + * {@inheritDoc} */ + @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(UNMODIFIABLE); } + /** + * {@inheritDoc} + */ + @Override public Set vertexSet() { - return this.vertices; + return vertices; } -} -// End MaskSubgraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskVertexSet.java b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskVertexSet.java index 5daf2566859..5756cfc0519 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/MaskVertexSet.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/MaskVertexSet.java @@ -1,134 +1,84 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * MaskVertexSet.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom +/* + * (C) Copyright 2007-2023, by France Telecom and Contributors. * - * Original Author: Guillaume Boulmier and Contributors. + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.util.*; -import org.jgrapht.util.PrefetchIterator.*; +import java.io.*; +import java.util.*; +import java.util.function.*; /** * Helper for {@link MaskSubgraph}. * - * @author Guillaume Boulmier - * @since July 5, 2007 */ -class MaskVertexSet +class MaskVertexSet extends AbstractSet + implements Serializable { - //~ Instance fields -------------------------------------------------------- - - private MaskFunctor mask; - - private int size; + private static final long serialVersionUID = 3751931017141472763L; - private Set vertexSet; + private final Set vertexSet; + private final Predicate mask; - private transient TypeUtil vertexTypeDecl = null; - - //~ Constructors ----------------------------------------------------------- - - public MaskVertexSet(Set vertexSet, MaskFunctor mask) + public MaskVertexSet(Set vertexSet, Predicate mask) { this.vertexSet = vertexSet; this.mask = mask; - this.size = -1; } - //~ Methods ---------------------------------------------------------------- - /** - * @see java.util.Collection#contains(java.lang.Object) + * {@inheritDoc} */ + @Override public boolean contains(Object o) { - return - !this.mask.isVertexMasked(TypeUtil.uncheckedCast(o, vertexTypeDecl)) - && this.vertexSet.contains(o); + if (!vertexSet.contains(o)) { + return false; + } + V v = TypeUtil.uncheckedCast(o); + return !mask.test(v); } /** - * @see java.util.Set#iterator() + * {@inheritDoc} */ + @Override public Iterator iterator() { - return new PrefetchIterator(new MaskVertexSetNextElementFunctor()); + return vertexSet.stream().filter(mask.negate()).iterator(); } /** - * @see java.util.Set#size() + * {@inheritDoc} */ + @Override public int size() { - if (this.size == -1) { - this.size = 0; - for (Iterator iter = iterator(); iter.hasNext();) { - iter.next(); - this.size++; - } - } - return this.size; + return (int) vertexSet.stream().filter(mask.negate()).count(); } - //~ Inner Classes ---------------------------------------------------------- - - private class MaskVertexSetNextElementFunctor - implements NextElementFunctor + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { - private Iterator iter; - - public MaskVertexSetNextElementFunctor() - { - this.iter = MaskVertexSet.this.vertexSet.iterator(); - } - - public V nextElement() - throws NoSuchElementException - { - V element = this.iter.next(); - while (MaskVertexSet.this.mask.isVertexMasked(element)) { - element = this.iter.next(); - } - return element; - } + return !iterator().hasNext(); } } - -// End MaskVertexSet.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/Multigraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/Multigraph.java index 5a9150f9d65..a498bfb385f 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/Multigraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/Multigraph.java @@ -1,85 +1,94 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------- - * Multigraph.java - * --------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 edges between any two vertices are. If you're - * unsure about multigraphs, see: + * 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 - implements UndirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -8313058939737164595L; - private static final long serialVersionUID = 3257001055819871795L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public Multigraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * Creates a new multigraph. - * + * 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 Multigraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new Multigraph<>(edgeClass)); } /** - * Creates a new multigraph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 Multigraph(EdgeFactory ef) + public static GraphBuilder> createBuilder(Supplier edgeSupplier) { - super(ef, true, false); + return new GraphBuilder<>(new Multigraph<>(null, edgeSupplier, false)); } -} -// End Multigraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/ParanoidGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/ParanoidGraph.java index 9b6983ae095..aab6ec0c62c 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/ParanoidGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/ParanoidGraph.java @@ -1,79 +1,55 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2007-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------- - * ParanoidGraph.java - * ------------------- - * (C) Copyright 2007-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 8-Nov-2007 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - 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. + * 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 - * @version $Id$ */ public class ParanoidGraph extends GraphDelegator { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = 5075284167422166539L; /** + * Create a new paranoid graph. + * + * @param g the underlying wrapped graph */ - private static final long serialVersionUID = 5075284167422166539L; - - //~ Constructors ----------------------------------------------------------- - public ParanoidGraph(Graph g) { super(g); } - //~ Methods ---------------------------------------------------------------- - /** - * @see Graph#addEdge(Object, Object, Object) + * {@inheritDoc} */ + @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { verifyAdd(edgeSet(), e); @@ -81,8 +57,9 @@ public boolean addEdge(V sourceVertex, V targetVertex, E e) } /** - * @see Graph#addVertex(Object) + * {@inheritDoc} */ + @Override public boolean addVertex(V v) { verifyAdd(vertexSet(), v); @@ -97,14 +74,10 @@ private static void verifyAdd(Set set, T t) } 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()"); + "ParanoidGraph detected objects " + "o1 (hashCode=" + o.hashCode() + + ") and o2 (hashCode=" + t.hashCode() + ") where o1.equals(o2) " + + "but o1.hashCode() != o2.hashCode()"); } } } } - -// End ParanoidGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/Pseudograph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/Pseudograph.java index 1b4ac168b26..0c2cb1f9d00 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/Pseudograph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/Pseudograph.java @@ -1,84 +1,93 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * Pseudograph.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 edges are permitted. If you're unsure about - * pseudographs, see: + * 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 - implements UndirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = -7574564204896552581L; - private static final long serialVersionUID = 3833183614484755253L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public Pseudograph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * Creates a new pseudograph. - * + * 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 Pseudograph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new Pseudograph<>(edgeClass)); } /** - * Creates a new pseudograph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 Pseudograph(EdgeFactory ef) + public static GraphBuilder> createBuilder(Supplier edgeSupplier) { - super(ef, true, true); + return new GraphBuilder<>(new Pseudograph<>(null, edgeSupplier, false)); } -} -// End Pseudograph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedGraph.java index 115c7bd7fb3..03916d811b3 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedGraph.java @@ -1,82 +1,92 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------ - * SimpleDirectedGraph.java - * ------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A simple directed graph. A simple directed graph is a directed graph in which - * neither multiple edges between any two vertices nor loops are permitted. + * 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 - implements DirectedGraph { - //~ Static fields/initializers --------------------------------------------- + private static final long serialVersionUID = 1665314455034181409L; - private static final long serialVersionUID = 4049358608472879671L; + /** + * Creates a new graph. + * + * @param edgeClass class on which to base the edge supplier + */ + public SimpleDirectedGraph(Class edgeClass) + { + this(null, SupplierUtil.createSupplier(edgeClass), false); + } - //~ Constructors ----------------------------------------------------------- + /** + * 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()); + } /** - * Creates a new simple directed graph. - * + * 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 SimpleDirectedGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new SimpleDirectedGraph<>(edgeClass)); } /** - * Creates a new simple directed graph with the specified edge factory. - * - * @param ef the edge factory of the new graph. + * 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 SimpleDirectedGraph(EdgeFactory ef) + public static GraphBuilder> createBuilder( + Supplier edgeSupplier) { - super(ef, false, false); + return new GraphBuilder<>(new SimpleDirectedGraph<>(null, edgeSupplier, false)); } -} -// End SimpleDirectedGraph.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedWeightedGraph.java index 089b781455b..6f35f919e74 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedWeightedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedWeightedGraph.java @@ -1,83 +1,88 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------------- - * SimpleDirectedWeightedGraph.java - * -------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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. + * 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 - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3904960841681220919L; - - //~ Constructors ----------------------------------------------------------- + private static final long serialVersionUID = -3301373580757772501L; /** - * Creates a new simple directed weighted graph with the specified edge - * factory. + * Creates a new graph. * - * @param ef the edge factory of the new graph. + * @param edgeClass class on which to base the edge supplier */ - public SimpleDirectedWeightedGraph(EdgeFactory ef) + public SimpleDirectedWeightedGraph(Class edgeClass) { - super(ef); + this(null, SupplierUtil.createSupplier(edgeClass)); } /** - * Creates a new simple directed weighted graph. - * + * 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 SimpleDirectedWeightedGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new SimpleDirectedWeightedGraph<>(edgeClass)); } -} -// End SimpleDirectedWeightedGraph.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleGraph.java index 50e88e7548a..ff1941daeb7 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleGraph.java @@ -1,85 +1,95 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * SimpleGraph.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): CHristian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A simple graph. A simple graph is an undirected graph for which at most one - * edge connects any two vertices, and loops are not permitted. If you're unsure - * about simple graphs, see: - * http://mathworld.wolfram.com/SimpleGraph.html. + * 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 - implements UndirectedGraph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3545796589454112304L; - - //~ Constructors ----------------------------------------------------------- + private static final long serialVersionUID = 4607246833824317836L; /** - * Creates a new simple graph with the specified edge factory. + * Creates a new simple graph. * - * @param ef the edge factory of the new graph. + * @param edgeClass class on which to base the edge supplier */ - public SimpleGraph(EdgeFactory ef) + public SimpleGraph(Class edgeClass) { - super(ef, false, false); + 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 SimpleGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new SimpleGraph<>(edgeClass)); } -} -// End SimpleGraph.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleWeightedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleWeightedGraph.java index 1d2f3f0796e..c3b43002060 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleWeightedGraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleWeightedGraph.java @@ -1,82 +1,85 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------ - * SimpleWeightedGraph.java - * ------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import java.util.function.*; /** - * A simple weighted graph. A simple weighted graph is a simple graph for which - * edges are assigned weights. + * A simple weighted graph. + * + * @param the graph vertex type + * @param the graph edge type */ public class SimpleWeightedGraph extends SimpleGraph - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3906088949100655922L; - - //~ Constructors ----------------------------------------------------------- + private static final long serialVersionUID = -1568410577378365671L; /** - * Creates a new simple weighted graph with the specified edge factory. + * Creates a new simple weighted graph. * - * @param ef the edge factory of the new graph. + * @param edgeClass class on which to base the edge supplier */ - public SimpleWeightedGraph(EdgeFactory ef) + public SimpleWeightedGraph(Class edgeClass) { - super(ef); + 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 SimpleWeightedGraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new SimpleWeightedGraph<>(edgeClass)); } -} -// End SimpleWeightedGraph.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/Subgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/Subgraph.java deleted file mode 100644 index bfb7da5bf19..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/Subgraph.java +++ /dev/null @@ -1,546 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------- - * Subgraph.java - * ------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 26-Jul-2003 : Accurate constructors to avoid casting problems (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 23-Oct-2003 : Allowed non-listenable graph as base (BN); - * 07-Feb-2004 : Enabled serialization (BN); - * 11-Mar-2004 : Made generic (CH); - * 15-Mar-2004 : Integrity is now checked using Maps (CH); - * 20-Mar-2004 : Cancelled verification of element identity to base graph (BN); - * 21-Sep-2004 : Added induced subgraph (who?) - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.event.*; -import org.jgrapht.util.*; - - -/** - * 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, than 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}).

    - * - * @author Barak Naveh - * @see Graph - * @see Set - * @since Jul 18, 2003 - */ -public class Subgraph> - extends AbstractGraph - implements Serializable -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3208313055169665387L; - 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"; - - //~ Instance fields -------------------------------------------------------- - - // - Set edgeSet = new LinkedHashSet(); // friendly to improve performance - Set vertexSet = new LinkedHashSet(); // friendly to improve - - // performance - - // - private transient Set unmodifiableEdgeSet = null; - private transient Set unmodifiableVertexSet = null; - private G base; - private boolean isInduced = false; - - //~ Constructors ----------------------------------------------------------- - - /** - * 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 Subgraph(G base, Set vertexSubset, Set edgeSubset) - { - super(); - - this.base = base; - - if (edgeSubset == null) { - isInduced = true; - } - - if (base instanceof ListenableGraph) { - ((ListenableGraph) base).addGraphListener( - new BaseGraphListener()); - } - - addVerticesUsingFilter(base.vertexSet(), vertexSubset); - addEdgesUsingFilter(base.edgeSet(), 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 Subgraph(G base, Set vertexSubset) - { - this(base, vertexSubset, null); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see Graph#getAllEdges(Object, Object) - */ - public Set getAllEdges(V sourceVertex, V targetVertex) - { - Set edges = null; - - if (containsVertex(sourceVertex) && containsVertex(targetVertex)) { - edges = new ArrayUnenforcedSet(); - - Set baseEdges = base.getAllEdges(sourceVertex, targetVertex); - - for (Iterator iter = baseEdges.iterator(); iter.hasNext();) { - E e = iter.next(); - - if (edgeSet.contains(e)) { // add if subgraph also contains - // it - edges.add(e); - } - } - } - - return edges; - } - - /** - * @see Graph#getEdge(Object, Object) - */ - public E getEdge(V sourceVertex, V targetVertex) - { - Set edges = getAllEdges(sourceVertex, targetVertex); - - if ((edges == null) || edges.isEmpty()) { - return null; - } else { - return edges.iterator().next(); - } - } - - /** - * @see Graph#getEdgeFactory() - */ - public EdgeFactory getEdgeFactory() - { - return base.getEdgeFactory(); - } - - /** - * @see Graph#addEdge(Object, Object) - */ - 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 (Iterator iter = edges.iterator(); iter.hasNext();) { - E e = iter.next(); - - if (!containsEdge(e)) { - edgeSet.add(e); - - return e; - } - } - - return null; - } - - /** - * @see Graph#addEdge(Object, Object, Object) - */ - 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); - - if (containsEdge(e)) { - return false; - } else { - edgeSet.add(e); - - return true; - } - } - - /** - * 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 - * @throws IllegalArgumentException - * - * @see Subgraph - * @see Graph#addVertex(Object) - */ - public boolean addVertex(V v) - { - if (v == null) { - throw new NullPointerException(); - } - - if (!base.containsVertex(v)) { - throw new IllegalArgumentException(NO_SUCH_VERTEX_IN_BASE); - } - - if (containsVertex(v)) { - return false; - } else { - vertexSet.add(v); - - return true; - } - } - - /** - * @see Graph#containsEdge(Object) - */ - public boolean containsEdge(E e) - { - return edgeSet.contains(e); - } - - /** - * @see Graph#containsVertex(Object) - */ - public boolean containsVertex(V v) - { - return vertexSet.contains(v); - } - - /** - * @see Graph#edgeSet() - */ - public Set edgeSet() - { - if (unmodifiableEdgeSet == null) { - unmodifiableEdgeSet = Collections.unmodifiableSet(edgeSet); - } - - return unmodifiableEdgeSet; - } - - /** - * @see Graph#edgesOf(Object) - */ - public Set edgesOf(V vertex) - { - assertVertexExist(vertex); - - Set edges = new ArrayUnenforcedSet(); - Set baseEdges = base.edgesOf(vertex); - - for (E e : baseEdges) { - if (containsEdge(e)) { - edges.add(e); - } - } - - return edges; - } - - /** - * @see Graph#removeEdge(Object) - */ - public boolean removeEdge(E e) - { - return edgeSet.remove(e); - } - - /** - * @see Graph#removeEdge(Object, Object) - */ - public E removeEdge(V sourceVertex, V targetVertex) - { - E e = getEdge(sourceVertex, targetVertex); - - return edgeSet.remove(e) ? e : null; - } - - /** - * @see Graph#removeVertex(Object) - */ - 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); - } - - /** - * @see Graph#vertexSet() - */ - public Set vertexSet() - { - if (unmodifiableVertexSet == null) { - unmodifiableVertexSet = Collections.unmodifiableSet(vertexSet); - } - - return unmodifiableVertexSet; - } - - /** - * @see Graph#getEdgeSource(Object) - */ - public V getEdgeSource(E e) - { - return base.getEdgeSource(e); - } - - /** - * @see Graph#getEdgeTarget(Object) - */ - public V getEdgeTarget(E e) - { - return base.getEdgeTarget(e); - } - - private void addEdgesUsingFilter(Set edgeSet, Set filter) - { - E e; - boolean containsVertices; - boolean edgeIncluded; - - for (Iterator iter = edgeSet.iterator(); iter.hasNext();) { - e = iter.next(); - - V sourceVertex = base.getEdgeSource(e); - V targetVertex = base.getEdgeTarget(e); - containsVertices = - containsVertex(sourceVertex) - && containsVertex(targetVertex); - - // note the use of short circuit evaluation - edgeIncluded = (filter == null) || filter.contains(e); - - if (containsVertices && edgeIncluded) { - addEdge(sourceVertex, targetVertex, e); - } - } - } - - private void addVerticesUsingFilter(Set vertexSet, Set filter) - { - V v; - - for (Iterator iter = vertexSet.iterator(); iter.hasNext();) { - v = iter.next(); - - // note the use of short circuit evaluation - if ((filter == null) || filter.contains(v)) { - addVertex(v); - } - } - } - - public G getBase() - { - return base; - } - - /** - * @see Graph#getEdgeWeight(Object) - */ - public double getEdgeWeight(E e) - { - return base.getEdgeWeight(e); - } - - /** - * @see WeightedGraph#setEdgeWeight(Object, double) - */ - public void setEdgeWeight(E e, double weight) - { - ((WeightedGraph) base).setEdgeWeight(e, weight); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * An internal listener on the base graph. - * - * @author Barak Naveh - * @since Jul 20, 2003 - */ - private class BaseGraphListener - implements GraphListener, - Serializable - { - private static final long serialVersionUID = 4343535244243546391L; - - /** - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - 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); - } - } - } - - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) - { - E edge = e.getEdge(); - - removeEdge(edge); - } - - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ - public void vertexAdded(GraphVertexChangeEvent e) - { - // we don't care - } - - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ - public void vertexRemoved(GraphVertexChangeEvent e) - { - V vertex = e.getVertex(); - - removeVertex(vertex); - } - } -} - -// End Subgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedGraphUnion.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedGraphUnion.java deleted file mode 100644 index f6c4577263d..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedGraphUnion.java +++ /dev/null @@ -1,81 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * UndirectedGraphUnion.java - * ------------------------- - * (C) Copyright 2009-2009, by Ilya Razenshteyn - * - * Original Author: Ilya Razenshteyn and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 02-Feb-2009 : Initial revision (IR); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -public class UndirectedGraphUnion - extends GraphUnion> - implements UndirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = -740199233080172450L; - - //~ Constructors ----------------------------------------------------------- - - UndirectedGraphUnion( - UndirectedGraph g1, - UndirectedGraphUnion g2, - WeightCombiner operator) - { - super(g1, g2, operator); - } - - UndirectedGraphUnion( - UndirectedGraph g1, - UndirectedGraphUnion g2) - { - super(g1, g2); - } - - //~ Methods ---------------------------------------------------------------- - - public int degreeOf(V vertex) - { - Set res = edgesOf(vertex); - return res.size(); - } -} - -// End UndirectedGraphUnion.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedMaskSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedMaskSubgraph.java deleted file mode 100644 index 63c9fc64983..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedMaskSubgraph.java +++ /dev/null @@ -1,64 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * UndirectedMaskSubgraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * An undirected graph that is a {@link MaskSubgraph} on another graph. - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -public class UndirectedMaskSubgraph - extends MaskSubgraph - implements UndirectedGraph -{ - //~ Constructors ----------------------------------------------------------- - - public UndirectedMaskSubgraph( - UndirectedGraph base, - MaskFunctor mask) - { - super(base, mask); - } -} - -// End UndirectedMaskSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedSubgraph.java deleted file mode 100644 index 381fa991c92..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedSubgraph.java +++ /dev/null @@ -1,106 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------- - * UndirectedSubgraph.java - * ----------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * An undirected graph that is a subgraph on other graph. - * - * @see Subgraph - */ -public class UndirectedSubgraph - extends Subgraph> - implements UndirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3256728359772631350L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new undirected 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 UndirectedSubgraph( - UndirectedGraph base, - Set vertexSubset, - Set edgeSubset) - { - super(base, vertexSubset, edgeSubset); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see UndirectedGraph#degreeOf(Object) - */ - public int degreeOf(V vertex) - { - assertVertexExist(vertex); - - int degree = 0; - - for (E e : getBase().edgesOf(vertex)) { - if (containsEdge(e)) { - degree++; - - if (getEdgeSource(e).equals(getEdgeTarget(e))) { - degree++; - } - } - } - - return degree; - } -} - -// End UndirectedSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedWeightedSubgraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedWeightedSubgraph.java deleted file mode 100644 index b7bde416856..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UndirectedWeightedSubgraph.java +++ /dev/null @@ -1,82 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------- - * UndirectedWeightedSubgraph.java - * ------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * An undirected weighted graph that is a subgraph on other graph. - * - * @see Subgraph - */ -public class UndirectedWeightedSubgraph - extends UndirectedSubgraph - implements WeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3689346615735236409L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new undirected weighted 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 UndirectedWeightedSubgraph( - WeightedGraph base, - Set vertexSubset, - Set edgeSubset) - { - super((UndirectedGraph) base, vertexSubset, edgeSubset); - } -} - -// End UndirectedWeightedSubgraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UniformIntrusiveEdgesSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UniformIntrusiveEdgesSpecifics.java new file mode 100644 index 00000000000..2247096d29d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/UniformIntrusiveEdgesSpecifics.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2003-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableDirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableDirectedGraph.java deleted file mode 100644 index 67ec2417ca9..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableDirectedGraph.java +++ /dev/null @@ -1,74 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * UnmodifiableDirectedGraph.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * A directed graph that cannot be modified. - * - * @see UnmodifiableGraph - */ -public class UnmodifiableDirectedGraph - extends UnmodifiableGraph - implements DirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3978701783725913906L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new unmodifiable directed graph based on the specified backing - * graph. - * - * @param g the backing graph on which an unmodifiable graph is to be - * created. - */ - public UnmodifiableDirectedGraph(DirectedGraph g) - { - super(g); - } -} - -// End UnmodifiableDirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableGraph.java deleted file mode 100644 index 6455120a938..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableGraph.java +++ /dev/null @@ -1,164 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * UnmodifiableGraph.java - * ---------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.graph; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * 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.

    - * - * @author Barak Naveh - * @since Jul 24, 2003 - */ -public class UnmodifiableGraph - extends GraphDelegator - implements Serializable -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3544957670722713913L; - private static final String UNMODIFIABLE = "this graph is unmodifiable"; - - //~ Constructors ----------------------------------------------------------- - - /** - * 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 UnmodifiableGraph(Graph g) - { - super(g); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @see Graph#addEdge(Object, Object) - */ - public E addEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#addEdge(Object, Object, Object) - */ - public boolean addEdge(V sourceVertex, V targetVertex, E e) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#addVertex(Object) - */ - public boolean addVertex(V v) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllEdges(Collection) - */ - public boolean removeAllEdges(Collection edges) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllEdges(Object, Object) - */ - public Set removeAllEdges(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeAllVertices(Collection) - */ - public boolean removeAllVertices(Collection vertices) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeEdge(Object) - */ - public boolean removeEdge(E e) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeEdge(Object, Object) - */ - public E removeEdge(V sourceVertex, V targetVertex) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } - - /** - * @see Graph#removeVertex(Object) - */ - public boolean removeVertex(V v) - { - throw new UnsupportedOperationException(UNMODIFIABLE); - } -} - -// End UnmodifiableGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableUndirectedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableUndirectedGraph.java deleted file mode 100644 index 2024ac9824d..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/UnmodifiableUndirectedGraph.java +++ /dev/null @@ -1,74 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------------- - * UnmodifiableUndirectedGraph.java - * -------------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer - * - * $Id$ - * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 11-Mar-2004 : Made generic (CH) - * - */ -package org.jgrapht.graph; - -import org.jgrapht.*; - - -/** - * An undirected graph that cannot be modified. - * - * @see UnmodifiableGraph - */ -public class UnmodifiableUndirectedGraph - extends UnmodifiableGraph - implements UndirectedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3258134639355704624L; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new unmodifiable undirected graph based on the specified - * backing graph. - * - * @param g the backing graph on which an unmodifiable graph is to be - * created. - */ - public UnmodifiableUndirectedGraph(UndirectedGraph g) - { - super(g); - } -} - -// End UnmodifiableUndirectedGraph.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedIntrusiveEdgesSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedIntrusiveEdgesSpecifics.java new file mode 100644 index 00000000000..3cc94049f22 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedIntrusiveEdgesSpecifics.java @@ -0,0 +1,98 @@ +/* + * (C) Copyright 2003-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedMultigraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedMultigraph.java index 339f55e3942..e553149affa 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedMultigraph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedMultigraph.java @@ -1,85 +1,89 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------- - * WeightedMultigraph.java - * ----------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 edges between any two vertices - * are. The edges of a weighted multigraph have weights. If you're unsure about - * multigraphs, see: + * 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 - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3544671793370640696L; - - //~ Constructors ----------------------------------------------------------- + private static final long serialVersionUID = -6009321659287373874L; /** - * Creates a new weighted multigraph with the specified edge factory. + * Creates a new graph. * - * @param ef the edge factory of the new graph. + * @param edgeClass class on which to base the edge supplier */ - public WeightedMultigraph(EdgeFactory ef) + public WeightedMultigraph(Class edgeClass) { - super(ef); + this(null, SupplierUtil.createSupplier(edgeClass)); } /** - * Creates a new weighted multigraph. - * + * 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 WeightedMultigraph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new WeightedMultigraph<>(edgeClass)); } -} -// End WeightedMultigraph.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedPseudograph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedPseudograph.java index f8b1030614b..377e0b6d7ff 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedPseudograph.java +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedPseudograph.java @@ -1,85 +1,89 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------ - * WeightedPseudograph.java - * ------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 05-Aug-2003 : Initial revision (BN); - * 06-Aug-2005 : Made generic (CH); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +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 edges are permitted. The edges - * of a weighted pseudograph have weights. If you're unsure about pseudographs, - * see: + * 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 - implements WeightedGraph { - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3257290244524356152L; - - //~ Constructors ----------------------------------------------------------- + private static final long serialVersionUID = 3037964528481084240L; /** - * Creates a new weighted pseudograph with the specified edge factory. + * Creates a new weighted graph. * - * @param ef the edge factory of the new graph. + * @param edgeClass class on which to base the edge supplier */ - public WeightedPseudograph(EdgeFactory ef) + public WeightedPseudograph(Class edgeClass) { - super(ef); + this(null, SupplierUtil.createSupplier(edgeClass)); } /** - * Creates a new weighted pseudograph. - * + * 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 WeightedPseudograph(Class edgeClass) + public static GraphBuilder> createBuilder( + Class edgeClass) { - this(new ClassBasedEdgeFactory(edgeClass)); + return new GraphBuilder<>(new WeightedPseudograph<>(edgeClass)); } -} -// End WeightedPseudograph.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/builder/AbstractGraphBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/AbstractGraphBuilder.java new file mode 100644 index 00000000000..7f264fb62f9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/AbstractGraphBuilder.java @@ -0,0 +1,287 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphBuilder.java new file mode 100644 index 00000000000..4d758c96278 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphBuilder.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2015-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphTypeBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphTypeBuilder.java new file mode 100644 index 00000000000..3c37d7d6224 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphTypeBuilder.java @@ -0,0 +1,353 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/builder/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/package-info.java new file mode 100644 index 00000000000..8f6d4abb3cb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/builder/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Various builder for graphs. + */ +package org.jgrapht.graph.builder; diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/AsSynchronizedGraph.java b/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/AsSynchronizedGraph.java new file mode 100644 index 00000000000..b0dd3645713 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/AsSynchronizedGraph.java @@ -0,0 +1,1543 @@ +/* + * (C) Copyright 2018-2023, 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 {@code edgeSet} and {@code 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 {@code edgesOf}, {@code incomingEdgesOf} and {@code 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 {@code edgesOf}, {@code incomingEdgesOf} + * and {@code outgoingEdgesOf} methods. + * + * @return {@code true} if cache is in use, {@code 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 {@code true} if the graph uses copyless mode, {@code false} otherwise + */ + public boolean isCopyless() + { + return allVerticesSet.isCopyless(); + } + + /** + * Set the cache strategy for {@code edgesOf}, {@code incomingEdgesOf} and + * {@code outgoingEdgesOf} methods. + * + * @param cacheEnabled a flag whether to use cache for those methods, if {@code 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 {@code true} if the graph uses fair mode, {@code 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 {@code true} if the set uses copyless mode, {@code 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 {@code Spliterator} over the elements in the set's unmodifiable copy. + * + * @return a {@code Spliterator} over the elements in the backing set's unmodifiable + * copy. + */ + @Override + public Spliterator spliterator() + { + return getCopy().spliterator(); + } + + /** + * Return a sequential {@code Stream} with the backing set's unmodifiable copy as its + * source. + * + * @return a sequential {@code Stream} with the backing set's unmodifiable copy as its + * source. + */ + @Override + public Stream stream() + { + return getCopy().stream(); + } + + /** + * Return a possibly parallel {@code Stream} with the backing set's unmodifiable copy + * as its source. + * + * @return a possibly parallel {@code 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 {@code 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 {@code edgesOf}, + * {@code incomingEdgesOf} and {@code 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 {@code edgesOf}, + * {@code incomingEdgesOf} and {@code outgoingEdgesOf} methods. + * + * @return {@code true} if cache is in use, {@code false} if cache is not in use. + */ + boolean isCacheEnabled(); + } + + /** + * Don't use cache for AsSynchronizedGraph's {@code edgesOf}, {@code incomingEdgesOf} + * and {@code 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 {@code 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 {@code edgesOf}, {@code incomingEdgesOf} and + * {@code 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 {@code true} if cache will be used, {@code 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 {@code true} if constructed as copyless, {@code 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 {@code true} if constructed as fair mode, {@code 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/package-info.java new file mode 100644 index 00000000000..046a68117dd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, 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 + */ + +/** + * Implementations of various concurrent graph structures. + */ +package org.jgrapht.graph.concurrent; diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/graph/package-info.java new file mode 100644 index 00000000000..2784e7e22cb --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Implementations of various graphs. + */ +package org.jgrapht.graph; diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/package.html b/jgrapht-core/src/main/java/org/jgrapht/graph/package.html deleted file mode 100644 index 10fca41f85c..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/graph/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Implementations of various graphs. - - \ No newline at end of file diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/ArrayUnenforcedSetEdgeSetFactory.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/ArrayUnenforcedSetEdgeSetFactory.java new file mode 100644 index 00000000000..678c7cad555 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/ArrayUnenforcedSetEdgeSetFactory.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2003-2023, 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedEdgeContainer.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedEdgeContainer.java new file mode 100644 index 00000000000..d6979cdc454 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedEdgeContainer.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedSpecifics.java new file mode 100644 index 00000000000..f3bfc4a844b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedSpecifics.java @@ -0,0 +1,278 @@ +/* + * (C) Copyright 2015-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupDirectedSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupDirectedSpecifics.java new file mode 100644 index 00000000000..efba38ed053 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupDirectedSpecifics.java @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2015-2023, 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); + } + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupUndirectedSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupUndirectedSpecifics.java new file mode 100644 index 00000000000..67ab36f9030 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/FastLookupUndirectedSpecifics.java @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2015-2023, 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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/Specifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/Specifics.java new file mode 100644 index 00000000000..f856a37f3b6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/Specifics.java @@ -0,0 +1,174 @@ +/* + * (C) Copyright 2015-2023, 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 {@code null}, returns + * {@code 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 {@code null}. + * If any of the specified vertices is {@code null} returns {@code 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); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedEdgeContainer.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedEdgeContainer.java new file mode 100644 index 00000000000..3745f27b457 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedEdgeContainer.java @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2015-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedSpecifics.java new file mode 100644 index 00000000000..26ffb044d1a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/UndirectedSpecifics.java @@ -0,0 +1,295 @@ +/* + * (C) Copyright 2015-2023, 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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/package-info.java new file mode 100644 index 00000000000..ff233d62d78 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Implementations of specifics for various graph types. + */ +package org.jgrapht.graph.specifics; diff --git a/jgrapht-core/src/main/java/org/jgrapht/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/package-info.java new file mode 100644 index 00000000000..2d874576239 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2016-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * The front-end API's interfaces and classes, including {@link org.jgrapht.Graph}. + */ +package org.jgrapht; diff --git a/jgrapht-core/src/main/java/org/jgrapht/package.html b/jgrapht-core/src/main/java/org/jgrapht/package.html deleted file mode 100644 index 76a55673619..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -The front-end API's interfaces and classes, including {@link org.jgrapht.Graph}, -{@link org.jgrapht.DirectedGraph} and {@link org.jgrapht.UndirectedGraph}. - - diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/AbstractGraphIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/AbstractGraphIterator.java index 5ab37b57b15..ab23953d7eb 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/AbstractGraphIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/AbstractGraphIterator.java @@ -1,81 +1,85 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (barak_naveh@users.sourceforge.net) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* -------------------------- - * AbstractGraphIterator.java - * -------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 11-Aug-2003 : Adaptation to new event model (BN); - * 04-May-2004 : Made generic (CH) + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - +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. + * 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 - * @since Jul 19, 2003 */ public abstract class AbstractGraphIterator implements GraphIterator { - //~ Instance fields -------------------------------------------------------- - - private List> traversalListeners = - new ArrayList>(); - private boolean crossComponentTraversal = true; - private boolean reuseEvents = false; + 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; - //~ Methods ---------------------------------------------------------------- + protected final FlyweightEdgeEvent reusableEdgeEvent; + protected final FlyweightVertexEvent reusableVertexEvent; + protected final Graph graph; + protected boolean crossComponentTraversal; + protected boolean reuseEvents; /** - * Sets the cross component traversal flag - indicates whether to traverse - * the graph across connected components. - * - * @param crossComponentTraversal if true traverses across + * Create a new iterator + * + * @param graph the graph + * + * @throws NullPointerException if argument is {@code null} + */ + 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 {@code true} traverses across connected components. + * + * @throws IllegalArgumentException if the argument is invalid for this iterator */ public void setCrossComponentTraversal(boolean crossComponentTraversal) { @@ -83,61 +87,46 @@ public void setCrossComponentTraversal(boolean crossComponentTraversal) } /** - * Test whether this iterator is set to traverse the graph across connected - * components. + * Test whether this iterator is set to traverse the graph across connected components. * - * @return true if traverses across connected components, - * otherwise false. + * @return {@code true} if traverses across connected components, otherwise + * {@code false}. */ + @Override public boolean isCrossComponentTraversal() { return crossComponentTraversal; } - /** - * @see GraphIterator#setReuseEvents(boolean) - */ + @Override public void setReuseEvents(boolean reuseEvents) { this.reuseEvents = reuseEvents; } - /** - * @see GraphIterator#isReuseEvents() - */ + @Override public boolean isReuseEvents() { return reuseEvents; } - /** - * Adds the specified traversal listener to this iterator. - * - * @param l the traversal listener to be added. - */ + @Override public void addTraversalListener(TraversalListener l) { - if (!traversalListeners.contains(l)) { - traversalListeners.add(l); - nListeners = traversalListeners.size(); - } + traversalListeners.add(l); + nListeners = traversalListeners.size(); } /** - * Unsupported. - * - * @throws UnsupportedOperationException + * @throws UnsupportedOperationException {@inheritDoc} */ + @Override public void remove() { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("remove"); } - /** - * Removes the specified traversal listener from this iterator. - * - * @param l the traversal listener to be removed. - */ + @Override public void removeTraversalListener(TraversalListener l) { traversalListeners.remove(l); @@ -145,31 +134,25 @@ public void removeTraversalListener(TraversalListener l) } /** - * Informs all listeners that the traversal of the current connected - * component finished. + * Informs all listeners that the traversal of the current connected component finished. * * @param e the connected component finished event. */ - protected void fireConnectedComponentFinished( - ConnectedComponentTraversalEvent e) + protected void fireConnectedComponentFinished(ConnectedComponentTraversalEvent e) { - for (int i = 0; i < nListeners; i++) { - TraversalListener l = traversalListeners.get(i); + for (TraversalListener l : traversalListeners) { l.connectedComponentFinished(e); } } /** - * Informs all listeners that a traversal of a new connected component has - * started. + * 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) + protected void fireConnectedComponentStarted(ConnectedComponentTraversalEvent e) { - for (int i = 0; i < nListeners; i++) { - TraversalListener l = traversalListeners.get(i); + for (TraversalListener l : traversalListeners) { l.connectedComponentStarted(e); } } @@ -179,10 +162,9 @@ protected void fireConnectedComponentStarted( * * @param e the edge traversal event. */ - protected void fireEdgeTraversed(EdgeTraversalEvent e) + protected void fireEdgeTraversed(EdgeTraversalEvent e) { - for (int i = 0; i < nListeners; i++) { - TraversalListener l = traversalListeners.get(i); + for (TraversalListener l : traversalListeners) { l.edgeTraversed(e); } } @@ -194,8 +176,7 @@ protected void fireEdgeTraversed(EdgeTraversalEvent e) */ protected void fireVertexTraversed(VertexTraversalEvent e) { - for (int i = 0; i < nListeners; i++) { - TraversalListener l = traversalListeners.get(i); + for (TraversalListener l : traversalListeners) { l.vertexTraversed(e); } } @@ -207,11 +188,105 @@ protected void fireVertexTraversed(VertexTraversalEvent e) */ protected void fireVertexFinished(VertexTraversalEvent e) { - for (int i = 0; i < nListeners; i++) { - TraversalListener l = traversalListeners.get(i); + for (TraversalListener l : traversalListeners) { l.vertexFinished(e); } } -} -// End AbstractGraphIterator.java + /** + * 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, E edge) + { + super(eventSource, edge); + } + + /** + * Sets the edge of this event. + * + * @param edge the edge to be set. + */ + protected void setEdge(E 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, V vertex) + { + super(eventSource, vertex); + } + + /** + * Sets the vertex of this event. + * + * @param vertex the vertex to be set. + */ + protected void setVertex(V vertex) + { + this.vertex = vertex; + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/BreadthFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/BreadthFirstIterator.java index a0535881ae6..eceee1f17cb 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/BreadthFirstIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/BreadthFirstIterator.java @@ -1,88 +1,61 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BreadthFirstIterator.java - * ------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): Liviu Rau - * Christian Hammer - * Ross Judson + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * 06-Aug-2003 : Extracted common logic to TraverseUtils.XXFirstIterator (BN); - * 31-Jan-2004 : Reparented and changed interface to parent class (BN); - * 28-Sep-2008 : Optimized using ArrayDeque per suggestion from Ross (JVS) + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; +import java.util.*; /** - * A breadth-first iterator for a directed and an 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. + * 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 - * @since Jul 19, 2003 */ public class BreadthFirstIterator - extends CrossComponentIterator + extends CrossComponentIterator> { - //~ Instance fields -------------------------------------------------------- - - private Deque queue = new ArrayDeque(); - - //~ Constructors ----------------------------------------------------------- + private Deque queue = new ArrayDeque<>(); /** * Creates a new breadth-first iterator for the specified graph. * * @param g the graph to be iterated. + * + * @throws NullPointerException if argument is {@code null} */ public BreadthFirstIterator(Graph g) { - this(g, null); + 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. + * 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 {@code 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. @@ -92,39 +65,146 @@ public BreadthFirstIterator(Graph g, V startVertex) super(g, startVertex); } - //~ Methods ---------------------------------------------------------------- + /** + * 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 {@code 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); + } /** - * @see CrossComponentIterator#isConnectedComponentExhausted() + * {@inheritDoc} */ + @Override protected boolean isConnectedComponentExhausted() { return queue.isEmpty(); } /** - * @see CrossComponentIterator#encounterVertex(Object, Object) + * {@inheritDoc} */ + @Override protected void encounterVertex(V vertex, E edge) { - putSeenData(vertex, null); + int depth = (edge == null ? 0 + : getSeenData(Graphs.getOppositeVertex(graph, edge, vertex)).depth + 1); + putSeenData(vertex, new SearchNodeData<>(edge, depth)); queue.add(vertex); } /** - * @see CrossComponentIterator#encounterVertexAgain(Object, Object) + * {@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(); } -} -// End BreadthFirstIterator.java + /** + * 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/ClosestFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/ClosestFirstIterator.java index 970fb383e69..58940de2b96 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/ClosestFirstIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/ClosestFirstIterator.java @@ -1,76 +1,54 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ------------------------- - * ClosestFirstIterator.java - * ------------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): Barak Naveh + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 02-Sep-2003 : Initial revision (JVS); - * 31-Jan-2004 : Reparented and changed interface to parent class (BN); - * 29-May-2005 : Added radius support (JVS); - * 06-Jun-2005 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 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. + * 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 path length from a start vertex. - * 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.

    + *

    + * 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 - * @since Sep 2, 2003 */ public class ClosestFirstIterator - extends CrossComponentIterator>> + extends CrossComponentIterator>> { - //~ Instance fields -------------------------------------------------------- - /** * Priority queue of fringe vertices. */ - private FibonacciHeap> heap = - new FibonacciHeap>(); + private AddressableHeap> heap; /** * Maximum distance to search. @@ -79,57 +57,154 @@ public class ClosestFirstIterator private boolean initialized = false; - //~ Constructors ----------------------------------------------------------- + /** + * 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 {@code 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. + * + * @throws NullPointerException if {@code g} is {@code null} + */ + public ClosestFirstIterator(Graph g, V startVertex) + { + this(g, startVertex, Double.POSITIVE_INFINITY); + } /** - * Creates a new closest-first iterator for the specified graph. + * 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. + * + * @throws NullPointerException if {@code g} is {@code null} */ - public ClosestFirstIterator(Graph g) + public ClosestFirstIterator(Graph g, Iterable startVertices) { - this(g, null); + this(g, startVertices, Double.POSITIVE_INFINITY); } /** - * 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. + * 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 {@code null}. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. + * @param radius limit on weighted path length, or {@link Double#POSITIVE_INFINITY} for unbounded + * search. + * + * @throws NullPointerException if {@code g} is {@code null} */ - public ClosestFirstIterator(Graph g, V startVertex) + public ClosestFirstIterator(Graph g, V startVertex, double radius) { - this(g, startVertex, Double.POSITIVE_INFINITY); + 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 length less than or equal to the - * specified radius. The specified start vertex may not be - * null. + * 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 {@code 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 path length, or Double.POSITIVE_INFINITY for - * unbounded search. + * @param radius limit on weighted path length, or {@link Double#POSITIVE_INFINITY} for unbounded + * search. + * @param heapSupplier supplier of the preferable heap implementation + * + * @throws NullPointerException if {@code g} is {@code null} */ - public ClosestFirstIterator(Graph g, V startVertex, double radius) + public ClosestFirstIterator( + Graph g, V startVertex, double radius, + Supplier>> heapSupplier) { - super(g, startVertex); + 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 {@code 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 {@link Double#POSITIVE_INFINITY} for unbounded + * search. + * + * @throws IllegalArgumentException if {@code startVertices} contains an element that is not a vertex of {@code g} + * @throws NullPointerException if {@code g} is {@code null} + */ + 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 {@code 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 {@link Double#POSITIVE_INFINITY} for unbounded + * search. + * @param heapSupplier supplier of the preferable heap implementation + * + * @throws IllegalArgumentException if {@code startVertices} contains an element that is not a vertex of {@code g} + * @throws NullPointerException if either one of {@code g} or {@code heapSupplier} is {@code null} + */ + 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); + } + } + } } - //~ Methods ---------------------------------------------------------------- - // override AbstractGraphIterator + @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (initialized) { @@ -139,18 +214,17 @@ public void setCrossComponentTraversal(boolean crossComponentTraversal) } /** - * Get the 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. + * 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 length of shortest path known, or Double.POSITIVE_INFINITY if no - * path found yet + * @return weighted length of shortest path known, or {@link Double#POSITIVE_INFINITY} if no path found + * yet */ public double getShortestPathLength(V vertex) { - FibonacciHeapNode> node = getSeenData(vertex); + AddressableHeap.Handle> node = getSeenData(vertex); if (node == null) { return Double.POSITIVE_INFINITY; @@ -160,37 +234,36 @@ public double getShortestPathLength(V vertex) } /** - * 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. + * 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 the start vertex. + * @return the spanning tree edge, or {@code null} if the vertex either has not been seen yet or is a + * start vertex. */ public E getSpanningTreeEdge(V vertex) { - FibonacciHeapNode> node = getSeenData(vertex); + AddressableHeap.Handle> node = getSeenData(vertex); if (node == null) { return null; } - return node.getData().spanningTreeEdge; + return node.getValue().spanningTreeEdge; } /** * @see CrossComponentIterator#isConnectedComponentExhausted() */ + @Override protected boolean isConnectedComponentExhausted() { if (heap.size() == 0) { return true; } else { - if (heap.min().getKey() > radius) { + if (heap.findMin().getKey() > radius) { heap.clear(); return true; @@ -203,6 +276,7 @@ protected boolean isConnectedComponentExhausted() /** * @see CrossComponentIterator#encounterVertex(Object, Object) */ + @Override protected void encounterVertex(V vertex, E edge) { double shortestPathLength; @@ -211,23 +285,24 @@ protected void encounterVertex(V vertex, E edge) } else { shortestPathLength = calculatePathLength(vertex, edge); } - FibonacciHeapNode> node = createSeenData(vertex, edge); - putSeenData(vertex, node); - heap.insert(node, shortestPathLength); + 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. + * 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) { - FibonacciHeapNode> node = getSeenData(vertex); + AddressableHeap.Handle> node = getSeenData(vertex); - if (node.getData().frozen) { + if (node.getValue().frozen) { // no improvement for this vertex possible return; } @@ -235,37 +310,36 @@ protected void encounterVertexAgain(V vertex, E edge) double candidatePathLength = calculatePathLength(vertex, edge); if (candidatePathLength < node.getKey()) { - node.getData().spanningTreeEdge = edge; - heap.decreaseKey(node, candidatePathLength); + node.getValue().spanningTreeEdge = edge; + node.decreaseKey(candidatePathLength); } } /** * @see CrossComponentIterator#provideNextVertex() */ + @Override protected V provideNextVertex() { - FibonacciHeapNode> node = heap.removeMin(); - node.getData().frozen = true; + AddressableHeap.Handle> node = heap.deleteMin(); + node.getValue().frozen = true; - return node.getData().vertex; + return node.getValue().vertex; } private void assertNonNegativeEdge(E edge) { if (getGraph().getEdgeWeight(edge) < 0) { - throw new IllegalArgumentException( - "negative edge weights not allowed"); + throw new IllegalArgumentException("negative edge weights not allowed"); } } /** - * Determine path length to a vertex via an edge, using the path length for - * the opposite vertex. + * 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) @@ -273,11 +347,9 @@ private double calculatePathLength(V vertex, E edge) assertNonNegativeEdge(edge); V otherVertex = Graphs.getOppositeVertex(getGraph(), edge, vertex); - FibonacciHeapNode> otherEntry = - getSeenData(otherVertex); + AddressableHeap.Handle> otherEntry = getSeenData(otherVertex); - return otherEntry.getKey() - + getGraph().getEdgeWeight(edge); + return otherEntry.getKey() + getGraph().getEdgeWeight(edge); } private void checkRadiusTraversal(boolean crossComponentTraversal) @@ -288,51 +360,30 @@ private void checkRadiusTraversal(boolean crossComponentTraversal) } } - /** - * The first time we see a vertex, make up a new heap node for it. - * - * @param vertex a vertex which has just been encountered. - * @param edge the edge via which the vertex was encountered. - * - * @return the new heap node. - */ - private FibonacciHeapNode> createSeenData( - V vertex, - E edge) - { - QueueEntry entry = new QueueEntry(); - entry.vertex = vertex; - entry.spanningTreeEdge = edge; - - return new FibonacciHeapNode>(entry); - } - - //~ Inner Classes ---------------------------------------------------------- - /** * Private data to associate with each entry in the priority queue. */ static class QueueEntry { /** - * Best spanning tree edge to vertex seen so far. + * The vertex reached. */ - E spanningTreeEdge; + V vertex; /** - * The vertex reached. + * Best spanning tree edge to vertex seen so far. */ - V vertex; + E spanningTreeEdge; /** * True once spanningTreeEdge is guaranteed to be the true minimum. */ boolean frozen; - QueueEntry() + QueueEntry(V vertex, E spanningTreeEdge) { + this.vertex = vertex; + this.spanningTreeEdge = spanningTreeEdge; } } } - -// End ClosestFirstIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/CrossComponentIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/CrossComponentIterator.java index dbf1599e759..deb0e70c1a2 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/CrossComponentIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/CrossComponentIterator.java @@ -1,190 +1,148 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* --------------------------- - * CrossComponentIterator.java - * --------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): John V. Sichi - * Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 31-Jul-2003 : Initial revision (BN); - * 11-Aug-2003 : Adaptation to new event model (BN); - * 31-Jan-2004 : Extracted cross-component traversal functionality (BN); - * 04-May-2004 : Made generic (CH) - * 07-May-2006 : Changed from List to Set (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.event.*; +import java.util.*; /** - * Provides a cross-connected-component traversal functionality for iterator - * subclasses. + * 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 - * @since Jan 31, 2004 */ public abstract class CrossComponentIterator extends AbstractGraphIterator { - //~ Static fields/initializers --------------------------------------------- - private static final int CCS_BEFORE_COMPONENT = 1; private static final int CCS_WITHIN_COMPONENT = 2; private static final int CCS_AFTER_COMPONENT = 3; - //~ Enums ------------------------------------------------------------------ - - /** - * 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 - } - - //~ Instance fields -------------------------------------------------------- - - // private final ConnectedComponentTraversalEvent ccFinishedEvent = new ConnectedComponentTraversalEvent( - this, - ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_FINISHED); + this, ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_FINISHED); private final ConnectedComponentTraversalEvent ccStartedEvent = new ConnectedComponentTraversalEvent( - this, - ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_STARTED); + this, ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_STARTED); - // TODO: support ConcurrentModificationException if graph modified - // during iteration. - private FlyweightEdgeEvent reusableEdgeEvent; - private FlyweightVertexEvent reusableVertexEvent; - private Iterator vertexIterator = null; + /** + * Stores the vertices that have been seen during iteration and (optionally) some additional + * traversal info regarding each vertex. + */ + private Map seen = new HashMap<>(); /** - * Stores the vertices that have been seen during iteration and (optionally) - * some additional traversal info regarding each vertex. + * Iterator which provides start vertices for cross-component iteration. */ - private Map seen = new HashMap(); - private V startVertex; - private Specifics specifics; + private Iterator entireGraphVertexIterator = null; - private final Graph graph; + /** + * 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; - //~ Constructors ----------------------------------------------------------- + /** + * Creates a new iterator for the specified graph. + * + * @param g the graph to be iterated + * + * @throws NullPointerException if argument is {@code null} + */ + 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. + * Creates a new iterator for the specified graph. Iteration will start at the specified start + * vertex. If the specified start vertex is {@code 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 + * @throws IllegalArgumentException if {@code g} does not contain {@code startVertex} + * @throws NullPointerException if {@code g} is {@code null} */ public CrossComponentIterator(Graph g, V startVertex) { - super(); - - if (g == null) { - throw new IllegalArgumentException("graph must not be null"); - } - graph = g; + this(g, startVertex == null ? null : Collections.singletonList(startVertex)); + } - specifics = createGraphSpecifics(g); - vertexIterator = g.vertexSet().iterator(); - setCrossComponentTraversal(startVertex == null); + /** + * Creates a new iterator for the specified graph. Iteration will start at the specified start + * vertices. If the specified start vertices is {@code 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 {@code startVertices} contains an element that is not + * a vertex of {@code g} + * @throws NullPointerException if {@code g} is {@code null} + */ + public CrossComponentIterator(Graph g, Iterable startVertices) + { + super(g); - reusableEdgeEvent = new FlyweightEdgeEvent(this, null); - reusableVertexEvent = new FlyweightVertexEvent(this, null); + /* + * Initialize crossComponentTraversal and test for containment + */ + if (startVertices == null) { + this.crossComponentTraversal = true; + } else { + this.crossComponentTraversal = false; + this.startVertexIterator = startVertices.iterator(); + } - if (startVertex == null) { - // pick a start vertex if graph not empty - if (vertexIterator.hasNext()) { - this.startVertex = vertexIterator.next(); - } else { - this.startVertex = null; + /* + * 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 if (g.containsVertex(startVertex)) { - this.startVertex = startVertex; } else { - throw new IllegalArgumentException( - "graph must contain the start vertex"); + this.startVertex = null; } - } - //~ Methods ---------------------------------------------------------------- - - /** - * @return the graph being traversed - */ - public Graph getGraph() - { - return graph; } - /** - * @see java.util.Iterator#hasNext() - */ + @Override public boolean hasNext() { if (startVertex != null) { @@ -199,30 +157,28 @@ public boolean hasNext() } } - if (isCrossComponentTraversal()) { - while (vertexIterator.hasNext()) { - V v = vertexIterator.next(); - - if (!isSeenVertex(v)) { - encounterVertex(v, null); - state = CCS_BEFORE_COMPONENT; - - return true; - } + 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 false; - } else { - return false; + return true; + } } + + return false; } else { return true; } } - /** - * @see java.util.Iterator#next() - */ + @Override public V next() { if (startVertex != null) { @@ -251,11 +207,25 @@ public V next() } /** - * Returns true if there are no more uniterated vertices in the - * currently iterated connected component; false otherwise. + * Lazily instantiates {@code entireGraphVertexIterator}. * - * @return true if there are no more uniterated vertices in the - * currently iterated connected component; false otherwise. + * @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 {@code true} if there are no more uniterated vertices in the currently iterated + * connected component; {@code false} otherwise. + * + * @return {@code true} if there are no more uniterated vertices in the currently iterated + * connected component; {@code false} otherwise. */ protected abstract boolean isConnectedComponentExhausted(); @@ -263,14 +233,14 @@ public V next() * 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 + * @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. + * Returns the vertex to be returned in the following call to the iterator {@code next} + * method. * * @return the next vertex to be returned by this iterator. */ @@ -281,10 +251,9 @@ public V next() * * @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. + * @return data associated with the seen vertex or {@code null} if no data was associated + * with the vertex. A {@code null} return can also indicate that the vertex was + * explicitly associated with {@code null}. */ protected D getSeenData(V vertex) { @@ -296,16 +265,15 @@ protected D getSeenData(V vertex) * * @param vertex vertex in question * - * @return true if vertex has already been seen + * @return {@code true} if vertex has already been seen */ - protected boolean isSeenVertex(Object vertex) + protected boolean isSeenVertex(V vertex) { return seen.containsKey(vertex); } /** - * Called whenever we re-encounter a vertex. The default implementation does - * nothing. + * 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 @@ -318,10 +286,10 @@ protected boolean isSeenVertex(Object vertex) * @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. + * @return previous value associated with specified vertex or {@code null} + * if no data was associated with the vertex. A {@code null} return + * can also indicate that the vertex was explicitly associated with + * {@code null}. */ protected D putSeenData(V vertex, D data) { @@ -329,8 +297,8 @@ protected D putSeenData(V vertex, D data) } /** - * Called when a vertex has been finished (meaning is dependent on traversal - * represented by subclass). + * Called when a vertex has been finished (meaning is dependent on traversal represented by + * subclass). * * @param vertex vertex which has been finished */ @@ -341,26 +309,21 @@ protected void finishVertex(V vertex) } } - // ------------------------------------------------------------------------- /** - * @param - * @param - * @param g + * Selects the outgoing edges for a given vertex based on the source vertex and other traversal + * state. The default implementation returns all outgoing edges. * - * @return TODO Document me + * @param vertex vertex in question + * @return set of outgoing edges connected to the vertex */ - static Specifics createGraphSpecifics(Graph g) + protected Set selectOutgoingEdges(V vertex) { - if (g instanceof DirectedGraph) { - return new DirectedSpecifics((DirectedGraph) g); - } else { - return new UndirectedSpecifics(g); - } + return graph.outgoingEdgesOf(vertex); } private void addUnseenChildrenOf(V vertex) { - for (E edge : specifics.edgesOf(vertex)) { + for (E edge : selectOutgoingEdges(vertex)) { if (nListeners != 0) { fireEdgeTraversed(createEdgeTraversalEvent(edge)); } @@ -375,196 +338,10 @@ private void addUnseenChildrenOf(V vertex) } } - private EdgeTraversalEvent createEdgeTraversalEvent(E edge) - { - if (isReuseEvents()) { - reusableEdgeEvent.setEdge(edge); - - return reusableEdgeEvent; - } else { - return new EdgeTraversalEvent(this, edge); - } - } - - private VertexTraversalEvent createVertexTraversalEvent(V vertex) - { - if (isReuseEvents()) { - reusableVertexEvent.setVertex(vertex); - - return reusableVertexEvent; - } else { - return new VertexTraversalEvent(this, vertex); - } - } - private void encounterStartVertex() { encounterVertex(startVertex, null); startVertex = null; } - //~ Inner Interfaces ------------------------------------------------------- - - static interface SimpleContainer - { - /** - * Tests if this container is empty. - * - * @return true if empty, otherwise false. - */ - public boolean isEmpty(); - - /** - * Adds the specified object to this container. - * - * @param o the object to be added. - */ - public void add(T o); - - /** - * Remove an object from this container and return it. - * - * @return the object removed from this container. - */ - public T remove(); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * Provides unified interface for operations that are different in directed - * graphs and in undirected graphs. - */ - abstract static class Specifics - { - /** - * Returns the edges outgoing from the specified vertex in case of - * directed graph, and the edge touching the specified vertex in case of - * undirected graph. - * - * @param vertex the vertex whose outgoing edges are to be returned. - * - * @return the edges outgoing from the specified vertex in case of - * directed graph, and the edge touching the specified vertex in case of - * undirected graph. - */ - public abstract Set edgesOf(VV vertex); - } - - /** - * A reusable edge event. - * - * @author Barak Naveh - * @since Aug 11, 2003 - */ - static class FlyweightEdgeEvent - extends EdgeTraversalEvent - { - private static final long serialVersionUID = 4051327833765000755L; - - /** - * @see EdgeTraversalEvent#EdgeTraversalEvent(Object, 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 - * @since Aug 11, 2003 - */ - static class FlyweightVertexEvent - extends VertexTraversalEvent - { - private static final long serialVersionUID = 3834024753848399924L; - - /** - * @see VertexTraversalEvent#VertexTraversalEvent(Object, Object) - */ - 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; - } - } - - /** - * An implementation of {@link Specifics} for a directed graph. - */ - private static class DirectedSpecifics - extends Specifics - { - private DirectedGraph graph; - - /** - * Creates a new DirectedSpecifics object. - * - * @param g the graph for which this specifics object to be created. - */ - public DirectedSpecifics(DirectedGraph g) - { - graph = g; - } - - /** - * @see CrossComponentIterator.Specifics#edgesOf(Object) - */ - public Set edgesOf(VV vertex) - { - return graph.outgoingEdgesOf(vertex); - } - } - - /** - * An implementation of {@link Specifics} in which edge direction (if any) - * is ignored. - */ - private static class UndirectedSpecifics - extends Specifics - { - private Graph graph; - - /** - * Creates a new UndirectedSpecifics object. - * - * @param g the graph for which this specifics object to be created. - */ - public UndirectedSpecifics(Graph g) - { - graph = g; - } - - /** - * @see CrossComponentIterator.Specifics#edgesOf(Object) - */ - public Set edgesOf(VV vertex) - { - return graph.edgesOf(vertex); - } - } } - -// End CrossComponentIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/DegeneracyOrderingIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/DegeneracyOrderingIterator.java new file mode 100644 index 00000000000..00291e2ef5e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/DegeneracyOrderingIterator.java @@ -0,0 +1,191 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.lang.reflect.Array; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.jgrapht.Graph; +import org.jgrapht.Graphs; + +/** + * 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 + * + * @throws NullPointerException if argument is {@code null} + */ + @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 LinkedHashSet<>(); + } + 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; + } + + /** + * @throws IllegalArgumentException if disabling the cross components nature of this iterator is attempted + */ + @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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/DepthFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/DepthFirstIterator.java index fec48e9e774..b8ce6944f35 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/DepthFirstIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/DepthFirstIterator.java @@ -1,91 +1,73 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Liviu Rau and Contributors. * - * 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. - */ -/* ----------------------- - * DepthFirstIterator.java - * ----------------------- - * (C) Copyright 2003-2008, by Liviu Rau and Contributors. - * - * Original Author: Liviu Rau - * Contributor(s): Barak Naveh - * Christian Hammer - * Welson Sun - * Ross Judson + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 29-Jul-2003 : Initial revision (LR); - * 31-Jul-2003 : Fixed traversal across connected components (BN); - * 06-Aug-2003 : Extracted common logic to TraverseUtils.XXFirstIterator (BN); - * 31-Jan-2004 : Reparented and changed interface to parent class (BN); - * 04-May-2004 : Made generic (CH) - * 27-Aug-2006 : Added WHITE/GRAY/BLACK to fix bug reported by Welson Sun (JVS) - * 28-Sep-2008 : Optimized using ArrayDeque per suggestion from Ross (JVS) + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.util.*; +import java.util.*; /** - * A depth-first iterator for a directed and an 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. + * 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 - * @since Jul 29, 2003 */ public class DepthFirstIterator - extends CrossComponentIterator + extends CrossComponentIterator { - //~ Static fields/initializers --------------------------------------------- - /** - * 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. + * 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(); - //~ Instance fields -------------------------------------------------------- - /** - * @see #getStack + * Standard vertex visit state enumeration. */ - private Deque stack = new ArrayDeque(); - - private transient TypeUtil vertexTypeDecl = null; + 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 + } - //~ Constructors ----------------------------------------------------------- + private Deque stack = new ArrayDeque<>(); /** * Creates a new depth-first iterator for the specified graph. @@ -94,29 +76,45 @@ public class DepthFirstIterator */ public DepthFirstIterator(Graph g) { - this(g, null); + 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. + * 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 {@code 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. + * + * @throws IllegalArgumentException if {@code g} does not contain {@code startVertex} + * @throws NullPointerException if {@code g} is {@code null} */ public DepthFirstIterator(Graph g, V startVertex) { super(g, startVertex); } - //~ Methods ---------------------------------------------------------------- - /** - * @see CrossComponentIterator#isConnectedComponentExhausted() + * 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 {@code 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. + * + * @throws IllegalArgumentException if {@code startVertices} contains an element that is not + * a vertex of {@code g} + * @throws NullPointerException if {@code g} is {@code null} */ + public DepthFirstIterator(Graph g, Iterable startVertices) + { + super(g, startVertices); + } + + @Override protected boolean isConnectedComponentExhausted() { for (;;) { @@ -128,7 +126,7 @@ protected boolean isConnectedComponentExhausted() return false; } - // Found a sentinel: pop it, record the finish time, + // 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. @@ -139,18 +137,14 @@ protected boolean isConnectedComponentExhausted() } } - /** - * @see CrossComponentIterator#encounterVertex(Object, Object) - */ + @Override protected void encounterVertex(V vertex, E edge) { putSeenData(vertex, VisitColor.WHITE); stack.addLast(vertex); } - /** - * @see CrossComponentIterator#encounterVertexAgain(Object, Object) - */ + @Override protected void encounterVertexAgain(V vertex, E edge) { VisitColor color = getSeenData(vertex); @@ -162,7 +156,7 @@ protected void encounterVertexAgain(V vertex, E edge) } // Since we've encountered it before, and it's still WHITE, it - // *must* be on the stack. Use removeLastOccurrence on the + // *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. @@ -171,9 +165,7 @@ protected void encounterVertexAgain(V vertex, E edge) stack.addLast(vertex); } - /** - * @see CrossComponentIterator#provideNextVertex() - */ + @Override protected V provideNextVertex() { V v; @@ -185,7 +177,7 @@ protected V provideNextVertex() // 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, vertexTypeDecl); + v = TypeUtil.uncheckedCast(o); break; } } @@ -200,17 +192,16 @@ protected V provideNextVertex() private void recordFinish() { - V v = TypeUtil.uncheckedCast(stack.removeLast(), vertexTypeDecl); + 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). + * 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 */ @@ -219,5 +210,3 @@ public Deque getStack() return stack; } } - -// End DepthFirstIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/GraphIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/GraphIterator.java index 2cfa98f991e..dab81982456 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/GraphIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/GraphIterator.java @@ -1,114 +1,85 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (barak_naveh@users.sourceforge.net) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ------------------ - * GraphIterator.java - * ------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Christian Hammer + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 31-Jul-2003 : Initial revision (BN); - * 11-Aug-2003 : Adaptation to new event model (BN); - * 04-May-2004 : Made generic (CH) + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.event.*; +import java.util.*; /** * A graph iterator. * + * @param the graph vertex type + * @param the graph edge type + * * @author Barak Naveh - * @since Jul 31, 2003 */ public interface GraphIterator extends Iterator { - //~ Methods ---------------------------------------------------------------- - /** - * Test whether this iterator is set to traverse the grpah across connected - * components. + * Test whether this iterator is set to traverse the graph across connected components. * - * @return true if traverses across connected components, - * otherwise false. + * @return {@code true} if traverses across connected components, otherwise + * {@code false}. */ - public boolean isCrossComponentTraversal(); + boolean isCrossComponentTraversal(); /** - * 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, + * Tests whether the {@code reuseEvents} flag is set. If the flag is set to + * {@code 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. + * @return the value of the {@code reuseEvents} flag. */ - public void setReuseEvents(boolean reuseEvents); + boolean isReuseEvents(); /** - * 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. + * Sets a value the {@code reuseEvents} flag. If the {@code reuseEvents} flag is set to + * {@code 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. + * @param reuseEvents whether to reuse previously fired event objects instead of creating a new + * event object for each event. */ - public boolean isReuseEvents(); + void setReuseEvents(boolean reuseEvents); /** * Adds the specified traversal listener to this iterator. * * @param l the traversal listener to be added. */ - public void addTraversalListener(TraversalListener l); + void addTraversalListener(TraversalListener l); /** - * Unsupported. + * Removes the specified traversal listener from this iterator. * - * @throws UnsupportedOperationException + * @param l the traversal listener to be removed. */ - public void remove(); + void removeTraversalListener(TraversalListener l); /** - * Removes the specified traversal listener from this iterator. - * - * @param l the traversal listener to be removed. + * Unsupported. + * + * @throws UnsupportedOperationException always since operation is not supported */ - public void removeTraversalListener(TraversalListener l); + @Override + void remove(); } - -// End GraphIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java new file mode 100644 index 00000000000..4ddf5745b37 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java @@ -0,0 +1,411 @@ +/* + * (C) Copyright 2018-2023, 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 java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.jgrapht.Graph; +import org.jgrapht.GraphTests; +import org.jgrapht.Graphs; +import org.jgrapht.util.CollectionUtil; + +/** + * 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. + * + * @throws IllegalArgumentException if argument is not an undirected graph + * @throws NullPointerException if argument is {@code null} + */ + 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; + } + + /** + * @return {@code true} always, since this iterator doesn't care about connected components + */ + @Override + public boolean isCrossComponentTraversal() + { + return true; + } + + /** + * @throws IllegalArgumentException if disabling the cross components nature of this iterator is attempted + */ + @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 LinkedHashSet<>(); + 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 LinkedHashSet<>(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 LinkedHashSet<>(); + 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; + } + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/MaximumCardinalityIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/MaximumCardinalityIterator.java new file mode 100644 index 00000000000..1bef9c191af --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/MaximumCardinalityIterator.java @@ -0,0 +1,239 @@ +/* + * (C) Copyright 2018-2023, 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. + * + * @throws NullPointerException if argument is {@code null} + */ + 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; + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/NotDirectedAcyclicGraphException.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/NotDirectedAcyclicGraphException.java new file mode 100644 index 00000000000..aae9347312a --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/NotDirectedAcyclicGraphException.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2021-2023, by Kaiichiro Ota and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + +/** + * Thrown to indicate that {@link TopologicalOrderIterator} is used for a non-directed acyclic + * graph. Note that this class extends {@link IllegalArgumentException} for backward compatibility. + * + * @author Kaiichiro Ota + * @since 1.5.2 + */ +public class NotDirectedAcyclicGraphException + extends IllegalArgumentException +{ + private static final String GRAPH_IS_NOT_A_DAG = "Graph is not a DAG"; + + private static final long serialVersionUID = 1L; + + public NotDirectedAcyclicGraphException() + { + super(GRAPH_IS_NOT_A_DAG); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/RandomWalkVertexIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/RandomWalkVertexIterator.java new file mode 100644 index 00000000000..dd4345f09e9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/RandomWalkVertexIterator.java @@ -0,0 +1,173 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws IllegalArgumentException if {@code graph} does not contain {@code vertex} + * @throws NullPointerException if either one of {@code graph} or {@code vertex} is {@code null} + */ + 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 + * + * @throws IllegalArgumentException if {@code graph} does not contain {@code vertex} + * @throws NullPointerException if either one of {@code graph} or {@code vertex} is {@code null} + */ + 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 + * + * @throws IllegalArgumentException if {@code graph} does not contain {@code vertex} + * @throws NullPointerException if either one of {@code graph} or {@code vertex} is {@code null} + */ + 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); + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/TopologicalOrderIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/TopologicalOrderIterator.java index 4365373bf07..47bd7a35141 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/TopologicalOrderIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/TopologicalOrderIterator.java @@ -1,277 +1,201 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2004-2023, by Marden Neubert and Contributors. * - * 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. - */ -/* ----------------------------- - * TopologicalOrderIterator.java - * ----------------------------- - * (C) Copyright 2004-2008, by Marden Neubert and Contributors. - * - * Original Author: Marden Neubert - * Contributor(s): Barak Naveh, John V. Sichi + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 17-Dec-2004 : Initial revision (MN); - * 25-Apr-2005 : Fixes for start vertex order (JVS); - * 06-Jun-2005 : Made generic (CH); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.util.*; +import java.util.*; /** - * Implements topological order traversal for a directed acyclic graph. A - * topological sort is a permutation p of the vertices of a graph such - * that an edge (i,j) implies that i appears before j - * in p (Skiena 1990, p. 208). See also - * http://mathworld.wolfram.com/TopologicalSort.html. + * A topological ordering iterator for a directed acyclic graph. + * + *

    + * A topological order is a permutation {@code p} of the vertices of a graph such that an edge + * {@code (i,j)} implies that {@code i} appears before {@code j} in {@code 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 NotDirectedAcyclicGraphException}. * - *

    See "Algorithms in Java, Third Edition, Part 5: Graph Algorithms" by - * Robert Sedgewick and "Data Structures and Algorithms with Object-Oriented - * Design Patterns in Java" by Bruno R. Preiss for implementation alternatives. - * The latter can be found online at - * http://www.brpreiss.com/books/opus5/

    + *

    + * 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. * - *

    For this iterator to work correctly the graph must be acyclic, and must - * not be modified during iteration. Currently there are no means to ensure - * that, nor to fail-fast; the results with cyclic input (including self-loops) - * or concurrent modifications are undefined. To precheck a graph for cycles, - * consider using {@link org.jgrapht.alg.CycleDetector} or {@link - * org.jgrapht.alg.StrongConnectivityInspector}.

    + * @param the graph vertex type + * @param the graph edge type * * @author Marden Neubert - * @since Dec 18, 2004 + * @author Dimitrios Michail */ public class TopologicalOrderIterator - extends CrossComponentIterator + extends AbstractGraphIterator { - //~ Instance fields -------------------------------------------------------- - private Queue queue; private Map inDegreeMap; - - //~ Constructors ----------------------------------------------------------- + private int remainingVertices; + private V cur; /** - * Creates a new topological order iterator over the directed graph - * specified, with arbitrary tie-breaking in case of partial order. - * Traversal will start at one of the graph's sources. See the - * definition of source at - * http://mathworld.wolfram.com/Source.html. + * 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 dg the directed graph to be iterated. + * @param graph the directed graph to be iterated */ - public TopologicalOrderIterator(DirectedGraph dg) + public TopologicalOrderIterator(Graph graph) { - this(dg, new LinkedListQueue()); + this(graph, (Comparator) null); } /** - * Creates a new topological order iterator over the directed graph - * specified, with a user-supplied queue implementation to allow customized - * control over tie-breaking in case of partial order. Traversal will start - * at one of the graph's sources. See the definition of source at - * http://mathworld.wolfram.com/Source.html. + * 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 dg the directed graph to be iterated. - * @param queue queue to use for tie-break in case of partial order (e.g. a - * PriorityQueue can be used to break ties according to vertex priority); - * must be initially empty + * @param graph the directed graph to be iterated + * @param comparator comparator in order to break ties in case of partial order + * + * @throws NotDirectedAcyclicGraphException if {@code graph} is not a DAG */ - public TopologicalOrderIterator(DirectedGraph dg, Queue queue) + public TopologicalOrderIterator(Graph graph, Comparator comparator) { - this(dg, queue, new HashMap()); - } + super(graph); + GraphTests.requireDirected(graph); - // NOTE: This is a hack to deal with the fact that CrossComponentIterator - // needs to know the start vertex in its constructor - private TopologicalOrderIterator( - DirectedGraph dg, - Queue queue, - Map inDegreeMap) - { - this(dg, initialize(dg, queue, inDegreeMap)); - this.queue = queue; - this.inDegreeMap = inDegreeMap; - - // empty queue for non-empty graph would indicate presence of - // cycles (no roots found) - assert dg.vertexSet().isEmpty() || !queue.isEmpty(); - } - - // NOTE: This is intentionally private, because starting the sort "in the - // middle" doesn't make sense. - private TopologicalOrderIterator(DirectedGraph dg, V start) - { - super(dg, start); - } - - //~ Methods ---------------------------------------------------------------- + // create queue + if (comparator == null) { + this.queue = new ArrayDeque<>(); + } else { + this.queue = new PriorityQueue<>(comparator); + } - /** - * @see CrossComponentIterator#isConnectedComponentExhausted() - */ - protected boolean isConnectedComponentExhausted() - { - // FIXME jvs 25-Apr-2005: This isn't correct for a graph with more than - // one component. We will actually exhaust a connected component - // before the queue is empty, because initialize adds roots from all - // components to the queue. - return queue.isEmpty(); - } + // 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 NotDirectedAcyclicGraphException(); + } + d++; + } + inDegreeMap.put(v, new ModifiableInteger(d)); + if (d == 0) { + queue.offer(v); + } + } - /** - * @see CrossComponentIterator#encounterVertex(Object, Object) - */ - protected void encounterVertex(V vertex, E edge) - { - putSeenData(vertex, null); - decrementInDegree(vertex); + // record vertices count + this.remainingVertices = graph.vertexSet().size(); } /** - * @see CrossComponentIterator#encounterVertexAgain(Object, Object) + * @return {@code true} always, since this iterator does not care about components */ - protected void encounterVertexAgain(V vertex, E edge) + @Override + public boolean isCrossComponentTraversal() { - decrementInDegree(vertex); + return true; } /** - * @see CrossComponentIterator#provideNextVertex() + * @throws IllegalArgumentException if disabling the cross components nature of this iterator is attempted */ - protected V provideNextVertex() + @Override + public void setCrossComponentTraversal(boolean crossComponentTraversal) { - return queue.remove(); + if (!crossComponentTraversal) { + throw new IllegalArgumentException("Iterator is always cross-component"); + } } - /** - * Decrements the in-degree of a vertex. - * - * @param vertex the vertex whose in-degree will be decremented. - */ - private void decrementInDegree(V vertex) + @Override + public boolean hasNext() { - ModifiableInteger inDegree = inDegreeMap.get(vertex); - - if (inDegree.value > 0) { - inDegree.value--; - - if (inDegree.value == 0) { - queue.offer(vertex); - } + if (cur != null) { + return true; } + cur = advance(); + if (cur != null && nListeners != 0) { + fireVertexTraversed(createVertexTraversalEvent(cur)); + } + return cur != null; } - /** - * Initializes the internal traversal object structure. Sets up the internal - * queue with the directed graph vertices and creates the control structure - * for the in-degrees. - * - * @param dg the directed graph to be iterated. - * @param queue initializer for queue - * @param inDegreeMap initializer for inDegreeMap - * - * @return start vertex - */ - private static V initialize( - DirectedGraph dg, - Queue queue, - Map inDegreeMap) + @Override + public V next() { - for (Iterator i = dg.vertexSet().iterator(); i.hasNext();) { - V vertex = i.next(); - - int inDegree = dg.inDegreeOf(vertex); - inDegreeMap.put(vertex, new ModifiableInteger(inDegree)); - - if (inDegree == 0) { - queue.offer(vertex); - } + if (!hasNext()) { + throw new NoSuchElementException(); } - if (queue.isEmpty()) { - return null; - } else { - return queue.peek(); + V result = cur; + cur = null; + if (nListeners != 0) { + fireVertexFinished(createVertexTraversalEvent(result)); } + return result; } - //~ Inner Classes ---------------------------------------------------------- - - // NOTE jvs 22-Dec-2006: For JDK1.4-compatibility, we can't assume - // that LinkedList implements Queue, since that wasn't introduced - // until JDK1.5, so use an adapter here. Move this to - // top-level in org.jgrapht.util if anyone else needs it. - private static class LinkedListQueue - extends LinkedList - implements Queue + private V advance() { - private static final long serialVersionUID = 4217659843476891334L; + V result = queue.poll(); - public T element() - { - return getFirst(); - } + if (result != null) { + for (E e : graph.outgoingEdgesOf(result)) { + V other = Graphs.getOppositeVertex(graph, e, result); - public boolean offer(T o) - { - return add(o); - } + ModifiableInteger inDegree = inDegreeMap.get(other); + if (inDegree.value > 0) { + inDegree.value--; - public T peek() - { - if (isEmpty()) { - return null; + if (inDegree.value == 0) { + queue.offer(other); + } + } } - return getFirst(); - } - public T poll() - { - if (isEmpty()) { - return null; + --remainingVertices; + } else { + /* + * Still expecting some vertices, but no vertex has zero degree. + */ + if (remainingVertices > 0) { + throw new NotDirectedAcyclicGraphException(); } - return removeFirst(); } - public T remove() - { - return removeFirst(); - } + return result; } -} -// End TopologicalOrderIterator.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/package-info.java new file mode 100644 index 00000000000..51136b1edc3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph traversal means. + */ +package org.jgrapht.traverse; diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/package.html b/jgrapht-core/src/main/java/org/jgrapht/traverse/package.html deleted file mode 100644 index 145444aba32..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Graph traversal means. - - \ No newline at end of file diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/AVLTree.java b/jgrapht-core/src/main/java/org/jgrapht/util/AVLTree.java new file mode 100644 index 00000000000..4268391ced9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/AVLTree.java @@ -0,0 +1,1154 @@ +/* + * (C) Copyright 2020-2023, 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); + } + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUnenforcedSet.java b/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUnenforcedSet.java index b9387febd22..88ad60c969d 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUnenforcedSet.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUnenforcedSet.java @@ -1,54 +1,35 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2006-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ----------------- - * ArrayUnenforcedSet.java - * ----------------- - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: John V. Sichi - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- - * 07-May-2006 : Initial version (JVS); + * 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. + * 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. + *

    + * 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 */ @@ -56,57 +37,66 @@ public class ArrayUnenforcedSet extends ArrayList implements Set { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = -7413250161201811238L; - //~ Constructors ----------------------------------------------------------- - + /** + * 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 {@code 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); } - //~ Methods ---------------------------------------------------------------- - + @Override public boolean equals(Object o) { return new SetForEquality().equals(o); } + @Override public int hashCode() { return new SetForEquality().hashCode(); } - //~ Inner Classes ---------------------------------------------------------- - /** * Multiple inheritance helper. */ private class SetForEquality extends AbstractSet { + @Override public Iterator iterator() { return ArrayUnenforcedSet.this.iterator(); } + @Override public int size() { return ArrayUnenforcedSet.this.size(); } } } - -// End ArrayUnenforcedSet.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUtil.java new file mode 100644 index 00000000000..9f701b2b12f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUtil.java @@ -0,0 +1,127 @@ +/* + * (C) Copyright 2021-2023, 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.Objects; + +/** + * 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 + * + * @throws NullPointerException if {@code arr == null} + * @throws ArrayIndexOutOfBoundsException if either one of {@code from} or {@code to} is out of bounds + */ + 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 + * + * @throws NullPointerException if {@code arr == null} + * @throws ArrayIndexOutOfBoundsException if either one of {@code from} or {@code to} is out of bounds + */ + public static final void reverse(int[] arr, int from, int to) + { + for (int i = from, j = to; i < j; ++i, --j) { + swap(arr, i, j); + } + } + + /** + * 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 + * + * @throws NullPointerException if {@code arr == null} + * @throws ArrayIndexOutOfBoundsException if either one of {@code i} or {@code j} is out of bounds + */ + public static final void swap(V[] arr, int i, int j) + { + Objects.requireNonNull(arr); + V tmp = arr[j]; + arr[j] = arr[i]; + arr[i] = tmp; + } + + /** + * Swaps the two elements at the specified indices in the given double array. + * + * @param arr the array + * @param i the index of the first element + * @param j the index of the second element + * + * @throws NullPointerException if {@code arr == null} + * @throws ArrayIndexOutOfBoundsException if either one of {@code i} or {@code j} is out of bounds + * + * @since 1.5.3 + */ + public static void swap(double[] arr, int i, int j) + { + Objects.requireNonNull(arr); + double tmp = arr[j]; + arr[j] = arr[i]; + arr[i] = tmp; + } + + /** + * Swaps the two elements at the specified indices in the given int array. + * + * @param arr the array + * @param i the index of the first element + * @param j the index of the second element + * + * @throws NullPointerException if {@code arr == null} + * @throws ArrayIndexOutOfBoundsException if either one of {@code i} or {@code j} is out of bounds + * + * @since 1.5.3 + */ + public static void swap(int[] arr, int i, int j) + { + Objects.requireNonNull(arr); + int tmp = arr[j]; + arr[j] = arr[i]; + arr[i] = tmp; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/CollectionUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/CollectionUtil.java new file mode 100644 index 00000000000..9fafe65781f --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/CollectionUtil.java @@ -0,0 +1,145 @@ +/* + * (C) Copyright 2020-2023, 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 expected size 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 expected number 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 expected number 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); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/ConcurrencyUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/ConcurrencyUtil.java new file mode 100644 index 00000000000..99f821a3911 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/ConcurrencyUtil.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2020-2023, 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 threads 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/DoublyLinkedList.java b/jgrapht-core/src/main/java/org/jgrapht/util/DoublyLinkedList.java new file mode 100644 index 00000000000..f33a03e9951 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/DoublyLinkedList.java @@ -0,0 +1,2031 @@ +/* + * (C) Copyright 2018-2023, 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 ListNode head = null; + private int size; + + /** + * Returns the first element of the list. + * + * @return the first element of the list + * + * @since 1.5.3 + */ + ListNode head() + { + return this.head; + } + + /** + * Returns the last element of the list. + * + * @return the last element of the list + */ + ListNode tail() + { + return head.getPrev(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() + { + return head == null; + } + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return size; + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() + { + if (!isEmpty()) { + ListNode node = head; + do { + ListNode next = node.getNext(); + 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(ListNode node) + { // call this before any modification of this list is done + if (node.getList() != null) { + String list = (node.getList() == this) ? "this" : "other"; + throw new IllegalArgumentException( + "Node <" + node + "> already contained in " + list + " list"); + } + node.setList(this); + size++; + modCount++; + } + + /** + * Atomically moves all {@link ListNode ListNodes} from {@code list} to this list as if each + * node was removed with {@link #removeListNode(ListNode)} from {@code list} and + * subsequently added to this list by {@link #addListNode(ListNode)}. + */ + private void moveAllListNodes(DoublyLinkedList list) + { // call this before any modification of this list is done + + for (ListNodeIteratorImpl it = list.new ListNodeIteratorImpl(0); it.hasNext();) { + ListNode node = it.nextNode(); + assert node.getList() == list; + node.setList(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(ListNode node) + { // call this before any modification of this list is done + if (node.list == this) { + + node.list = null; + node.setNext(null); + node.setPrev(null); + + size--; + modCount++; + return true; + } + return false; + } + + /** + * Establishes the links between the given {@link ListNode 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(ListNode predecessor, ListNode successor) + { + predecessor.setNext(successor); + successor.setPrev(predecessor); + } + + /** Insert non null {@code node} before non null {@code successor} into the list. */ + private void linkBefore(ListNode node, ListNode successor) + { + addListNode(node); + link(successor.getPrev(), node); + link(node, successor); + } + + /** Insert non null {@code node} as last node into the list. */ + private void linkLast(ListNode 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 { + ListNode refNode = index == previousSize ? head() : getNodeAt(index); + + ListNode listTail = list.tail(); + link(refNode.getPrev(), 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(ListNode node) + { + ListNode prev = node.getPrev(); + ListNode next = node.getNext(); + 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) + { + if (index == size) { // also true if this is empty + linkLast(node); + } else { + ListNode successor = index == 0 ? head : getNodeAt(index); + linkBefore(node, successor); + if (head == successor) { + head = node; + } + } + } + + /** + * 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) + { + if (successor.getList() != this) { + throw new IllegalArgumentException("Node <" + successor + "> not in this list"); + } + linkBefore(node, successor); + if (head == successor) { + head = node; + } + } + + /** + * 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 ListNode 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 ListNode getNodeAt(int index) + { + if (index < 0 || size <= index) { + throw new IndexOutOfBoundsException("Index: " + index); + } + ListNode node; + if (index < size / 2) { + node = head(); + for (int i = 0; i < index; i++) { + node = node.getNext(); + } + } else { + node = tail(); + for (int i = size - 1; index < i; i--) { + node = node.getPrev(); + } + } + 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; + } + ListNode current = head(); + for (int i = 0; i < size; i++) { + if (current == node) { + return i; + } + current = current.getNext(); + } + // 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 Objects.requireNonNull(node).getList() == 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(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(this::head, ListNode::getNext, 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, ListNode::getPrev, 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; + ListNode firstNode = first.get(); + ListNode node = firstNode; + do { + if (Objects.equals(node.getValue(), 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 getNode(index).getValue(); + } + + /** + * {@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. + * + * @see #reversed() + */ + public void invert() + { + if (size < 2) { + return; + } + ListNode newHead = tail(); + ListNode current = head(); + do { + ListNode next = current.getNext(); + + current.setNext(current.getPrev()); + current.setPrev(next); + + current = next; + } while (current != head); + head = newHead; + ++modCount; + } + + /** + * Returns a reverse-ordered view of this {@code DoublyLinkedList}. + * Unlike {@link #invert()} which modifies the called instance, + * this method returns a view, while keeping the original instance + * unmodified. + * + *

    The returned view is unmodifiable, i.e., any method that + * attempts to modify the returned view throws an {@link UnsupportedOperationException}. + * The behavior of the view is not defined if the original list is modified after + * the creation of the view. + * + * @return a reverse-ordered view of this {@code DoublyLinkedList} + * + * @see #invert() + * @since 1.5.3 + */ + public DoublyLinkedList reversed() { + return new ReversedDoublyLinkedListView<>(this); + } + + /** + * 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) + { + ListNode startNode = 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) + { + ListNode startNode = nodeOf(firstElement); + if (startNode == null) { + throw new NoSuchElementException(); + } + return reverseIterator(new ListNodeIteratorImpl(size(), startNode.getNext())); + } + + /** + * {@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(this::head, ListNode::getNext, element); + ListNode 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 ListNode next; + /** ListNode this iterator returned last. */ + private ListNode 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 = getNode(startIndex); + } + } + + private ListNodeIteratorImpl(int startIndex, ListNode 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 ListNode nextNode() + { + checkForComodification(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + + last = next; + next = next.getNext(); + nextIndex++; + return last; + } + + /** + * {@inheritDoc} + */ + @Override + public ListNode previousNode() + { + checkForComodification(); + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + + last = next = next.getPrev(); + 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.getNext(); + boolean wasLast = last == tail(); + removeNode(last); + if (wasLast) { // or the sole node + last = addElementLast(e); + } else { + last = addElementBeforeNode(nextNode, e); + } + expectedModCount += 2; // because of unlink and add + } + + /** + * {@inheritDoc} + */ + @Override + public void remove() + { + if (last == null) { + throw new IllegalStateException(); + } + checkForComodification(); + + ListNode lastsNext = last.getNext(); + 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(); + } + } + } + + /** + * A wrapper for {@link NodeIterator} that disallows modification + * of the underlying list. All getter methods forward the call + * to the wrapped iterator. + * + * @since 1.5.3 + */ + private static class UnmodifiableNodeIterator implements NodeIterator { + private NodeIterator orig; + + UnmodifiableNodeIterator(NodeIterator original) { + this.orig = original; + } + + @Override + public boolean hasNext() { + return orig.hasNext(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public ListNode nextNode() { + return orig.nextNode(); + } + + /** + * Returns the wrapped node iterator. + * @return the wrapped node iterator + */ + NodeIterator getWrapped() { + return orig; + } + } + + + /** + * A wrapper for {@link ListNodeIterator} that disallows modification + * of the underlying list. All setter methods throw + * {@link UnsupportedOperationException} and all getter methods + * forward the call to the wrapped iterator. + * + * @since 1.5.3 + */ + private static class UnmodifiableListNodeIterator extends UnmodifiableNodeIterator implements ListNodeIterator { + + UnmodifiableListNodeIterator(ListNodeIterator original) { + super(original); + } + + @Override + public boolean hasPrevious() { + return getWrapped().hasPrevious(); + } + + @Override + public int nextIndex() { + return getWrapped().nextIndex(); + } + + @Override + public int previousIndex() { + return getWrapped().previousIndex(); + } + + @Override + public void set(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public ListNode previousNode() { + return getWrapped().previousNode(); + } + + @Override + ListNodeIterator getWrapped() { + return (ListNodeIterator) super.getWrapped(); + } + } + + /** + * 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 abstract static class ListNode + { + + /** The list that this node is a member of. */ + private DoublyLinkedList list; + + /** + * Constructs a new {@code ListNode}. + */ + ListNode() { + } + + /** + * Returns the immutable value this {@code ListNode} contains. + * + * @return the value this list node contains + */ + public abstract 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 + */ + public abstract 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 + */ + public abstract ListNode getPrev(); + + /** + * Returns the list that this node is a member of. + * + * @return the list that this node is a member of + * + * @since 1.5.3 + */ + public DoublyLinkedList getList() { + return this.list; + } + + /** + * Sets the next node to the specified node. + * + * @param next the next node + * + * @throws UnsupportedOperationException if this node does not support modification + */ + abstract void setNext(ListNode next); + + /** + * Sets the previous node to the specified node. + * + * @param prev the previous node + * + * @throws UnsupportedOperationException if this node does not support modification + */ + abstract void setPrev(ListNode prev); + + /** + * Sets the list that this node belongs to. + * + * @param list the list to consist of this node + * + * @throws UnsupportedOperationException if this node does not support modification + */ + void setList(DoublyLinkedList list) { + this.list = list; + } + + /** + * Returns the string representation of this list node. + * + * @return the string representation of this list node + */ + @Override + public String toString() + { + if (getList() == null) { + return " - " + getValue() + " - "; // not in a list + } else { + return getPrev().getValue() + " -> " + getValue() + " -> " + getNext().getValue(); + } + } + } + + /** + * The default {@link ListNode} implementation that enables checks and enforcement of a single + * container list policy. + */ + private static class ListNodeImpl extends ListNode + { + /** The value stored by this node. */ + private final V value; + + private ListNode next = null; + private ListNode prev = null; + + /** + * Creates new list node + * + * @param value the value this list node stores + */ + ListNodeImpl(V value) + { + this.value = value; + } + + /** + * {@inheritDoc} + */ + @Override + public ListNode getNext() + { + return next; + } + + /** + * {@inheritDoc} + */ + @Override + public ListNode getPrev() + { + return prev; + } + + @Override + public final V getValue() { + return value; + } + + @Override + void setNext(ListNode next) { + this.next = next; + } + + @Override + void setPrev(ListNode prev) { + this.prev = prev; + } + + } + + /** + * Reversed view of a {@link ListNode}. + * + * @since 1.5.3 + */ + private static class ReversedListNode extends ListNode { + private final ListNode wrapped; + + ReversedListNode(ListNode node, DoublyLinkedList list) { + this.wrapped = node; + super.setList(list); + } + + @Override + public ReversedListNode getNext() + { + return new ReversedListNode<>(wrapped.getPrev(), this.getList()); + } + + @Override + public ReversedListNode getPrev() + { + return new ReversedListNode<>(wrapped.getNext(), this.getList()); + } + + @Override + public final V getValue() { + return wrapped.getValue(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + void setNext(ListNode next) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + void setPrev(ListNode prev) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + void setList(DoublyLinkedList list) { + throw new UnsupportedOperationException(); + } + + } + + /** + * A reversed view of a {@link DoublyLinkedList}. This view is unmodifiable. + * + * @since 1.5.3 + */ + private static class ReversedDoublyLinkedListView extends DoublyLinkedList { + + /** Reference to the original list. */ + private final DoublyLinkedList orig; + + /** + * Constructs a new reversed view of the specified {@code DoublyLinkedList}. + * + * @param orig the original list + * + * @throws NullPointerException if argument is {@code null} + */ + ReversedDoublyLinkedListView(DoublyLinkedList orig) { + this.orig = Objects.requireNonNull(orig); + } + + @Override + ReversedListNode head() + { + return new ReversedListNode<>(orig.tail(), this); + } + + @Override + ReversedListNode tail() + { + return new ReversedListNode<>(orig.head(), this); + } + + @Override + public boolean isEmpty() + { + return orig.isEmpty(); + } + + @Override + public int size() + { + return orig.size(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void clear() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addNode(int index, ListNode node) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addNodeFirst(ListNode node) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addNodeLast(ListNode node) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addNodeBefore(ListNode node, ListNode successor) + { + throw new UnsupportedOperationException(); + } + + @Override + public ListNode getNode(int index) + { + return new ReversedListNode<>(orig.getNodeAt(size() - (1 + index)), this); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeNode(ListNode node) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public ListNode addElementFirst(E element) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public ListNode addElementLast(E element) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public ListNode addElementBeforeNode(ListNode successor, E element) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void add(int index, E element) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E remove(int index) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addFirst(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void addLast(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean offerFirst(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean offerLast(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E removeFirst() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E removeLast() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E pollFirst() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E pollLast() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeFirstOccurrence(Object o) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeLastOccurrence(Object o) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean offer(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean remove(Object o) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E poll() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void push(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E pop() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void invert() + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void moveFrom(int index, DoublyLinkedList movedList) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void append(DoublyLinkedList movedList) + { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public void prepend(DoublyLinkedList movedList) + { + throw new UnsupportedOperationException(); + } + + @Override + public NodeIterator circularIterator(E firstElement) { + return new UnmodifiableNodeIterator<>(super.circularIterator(firstElement)); + } + + @Override + public NodeIterator reverseCircularIterator(E firstElement) { + return new UnmodifiableNodeIterator<>(super.reverseCircularIterator(firstElement)); + } + + @Override + public NodeIterator descendingIterator() { + return super.descendingIterator(); + } + + @Override + public ListNodeIterator listIterator(int index) { + return new UnmodifiableListNodeIterator<>(super.listIterator(index)); + } + + @Override + public ListNodeIterator listIterator(E element) { + return new UnmodifiableListNodeIterator<>(super.listIterator(element)); + } + + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/ElementsSequenceGenerator.java b/jgrapht-core/src/main/java/org/jgrapht/util/ElementsSequenceGenerator.java new file mode 100644 index 00000000000..cee977aaf03 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/ElementsSequenceGenerator.java @@ -0,0 +1,120 @@ +/* + * (C) Copyright 2020-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeap.java b/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeap.java deleted file mode 100644 index 569ed8c6aa1..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeap.java +++ /dev/null @@ -1,611 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (barak_naveh@users.sourceforge.net) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * FibonnaciHeap.java - * -------------------------- - * (C) Copyright 1999-2003, by Nathan Fiedler and Contributors. - * - * Original Author: Nathan Fiedler - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 03-Sept-2003 : Adapted from Nathan Fiedler (JVS); - * - * Name Date Description - * ---- ---- ----------- - * nf 08/31/97 Initial version - * nf 09/07/97 Removed FibHeapData interface - * nf 01/20/01 Added synchronization - * nf 01/21/01 Made Node an inner class - * nf 01/05/02 Added clear(), renamed empty() to - * isEmpty(), and renamed printHeap() - * to toString() - * nf 01/06/02 Removed all synchronization - * - */ -package org.jgrapht.util; - -import java.util.*; - - -/** - * This class implements a Fibonacci heap data structure. Much of the code in - * this class is based on the algorithms in the "Introduction to Algorithms"by - * Cormen, Leiserson, and Rivest in Chapter 21. The amortized running time of - * most of these methods is O(1), making it a very fast data structure. Several - * have an actual running time of O(1). removeMin() and delete() have O(log n) - * amortized running times because they do the heap consolidation. If you - * attempt to store nodes in this heap with key values of -Infinity - * (Double.NEGATIVE_INFINITY) the delete() operation may fail to - * remove the correct element. - * - *

    Note that this implementation is not synchronized. If multiple - * threads access a set concurrently, and at least one of the threads modifies - * the set, it must be synchronized externally. This is typically - * accomplished by synchronizing on some object that naturally encapsulates the - * set.

    - * - *

    This class was originally developed by Nathan Fiedler for the GraphMaker - * project. It was imported to JGraphT with permission, courtesy of Nathan - * Fiedler.

    - * - * @author Nathan Fiedler - */ -public class FibonacciHeap -{ - //~ Static fields/initializers --------------------------------------------- - - private static final double oneOverLogPhi = - 1.0 / Math.log((1.0 + Math.sqrt(5.0)) / 2.0); - - //~ Instance fields -------------------------------------------------------- - - /** - * Points to the minimum node in the heap. - */ - private FibonacciHeapNode minNode; - - /** - * Number of nodes in the heap. - */ - private int nNodes; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructs a FibonacciHeap object that contains no elements. - */ - public FibonacciHeap() - { - } // FibonacciHeap - - //~ Methods ---------------------------------------------------------------- - - /** - * Tests if the Fibonacci heap is empty or not. Returns true if the heap is - * empty, false otherwise. - * - *

    Running time: O(1) actual

    - * - * @return true if the heap is empty, false otherwise - */ - public boolean isEmpty() - { - return minNode == null; - } - - // isEmpty - - /** - * Removes all elements from this heap. - */ - public void clear() - { - minNode = null; - nNodes = 0; - } - - // clear - - /** - * Decreases the key value for a heap node, given the new value to take on. - * The structure of the heap may be changed and will not be consolidated. - * - *

    Running time: O(1) amortized

    - * - * @param x node to decrease the key of - * @param k new key value for node x - * - * @exception IllegalArgumentException Thrown if k is larger than x.key - * value. - */ - public void decreaseKey(FibonacciHeapNode x, double k) - { - if (k > x.key) { - throw new IllegalArgumentException( - "decreaseKey() got larger key value"); - } - - x.key = k; - - FibonacciHeapNode y = x.parent; - - if ((y != null) && (x.key < y.key)) { - cut(x, y); - cascadingCut(y); - } - - if (x.key < minNode.key) { - minNode = x; - } - } - - // decreaseKey - - /** - * Deletes a node from the heap given the reference to the node. The trees - * in the heap will be consolidated, if necessary. This operation may fail - * to remove the correct element if there are nodes with key value - * -Infinity. - * - *

    Running time: O(log n) amortized

    - * - * @param x node to remove from heap - */ - public void delete(FibonacciHeapNode x) - { - // make x as small as possible - decreaseKey(x, Double.NEGATIVE_INFINITY); - - // remove the smallest, which decreases n also - removeMin(); - } - - // delete - - /** - * Inserts a new data element into the heap. No heap consolidation is - * performed at this time, the new node is simply inserted into the root - * list of this heap. - * - *

    Running time: O(1) actual

    - * - * @param node new node to insert into heap - * @param key key value associated with data object - */ - public void insert(FibonacciHeapNode node, double key) - { - node.key = key; - - // concatenate node into min list - if (minNode != null) { - node.left = minNode; - node.right = minNode.right; - minNode.right = node; - node.right.left = node; - - if (key < minNode.key) { - minNode = node; - } - } else { - minNode = node; - } - - nNodes++; - } - - // insert - - /** - * Returns the smallest element in the heap. This smallest element is the - * one with the minimum key value. - * - *

    Running time: O(1) actual

    - * - * @return heap node with the smallest key - */ - public FibonacciHeapNode min() - { - return minNode; - } - - // min - - /** - * Removes the smallest element from the heap. This will cause the trees in - * the heap to be consolidated, if necessary. - * - *

    Running time: O(log n) amortized

    - * - * @return node with the smallest key - */ - public FibonacciHeapNode removeMin() - { - FibonacciHeapNode z = minNode; - - if (z != null) { - int numKids = z.degree; - FibonacciHeapNode x = z.child; - FibonacciHeapNode tempRight; - - // for each child of z do... - while (numKids > 0) { - tempRight = x.right; - - // remove x from child list - x.left.right = x.right; - x.right.left = x.left; - - // add x to root list of heap - x.left = minNode; - x.right = minNode.right; - minNode.right = x; - x.right.left = x; - - // set parent[x] to null - x.parent = null; - x = tempRight; - numKids--; - } - - // remove z from root list of heap - z.left.right = z.right; - z.right.left = z.left; - - if (z == z.right) { - minNode = null; - } else { - minNode = z.right; - consolidate(); - } - - // decrement size of heap - nNodes--; - } - - return z; - } - - // removeMin - - /** - * Returns the size of the heap which is measured in the number of elements - * contained in the heap. - * - *

    Running time: O(1) actual

    - * - * @return number of elements in the heap - */ - public int size() - { - return nNodes; - } - - // size - - /** - * Joins two Fibonacci heaps into a new one. No heap consolidation is - * performed at this time. The two root lists are simply joined together. - * - *

    Running time: O(1) actual

    - * - * @param h1 first heap - * @param h2 second heap - * - * @return new heap containing h1 and h2 - */ - public static FibonacciHeap union( - FibonacciHeap h1, - FibonacciHeap h2) - { - FibonacciHeap h = new FibonacciHeap(); - - if ((h1 != null) && (h2 != null)) { - h.minNode = h1.minNode; - - if (h.minNode != null) { - if (h2.minNode != null) { - h.minNode.right.left = h2.minNode.left; - h2.minNode.left.right = h.minNode.right; - h.minNode.right = h2.minNode; - h2.minNode.left = h.minNode; - - if (h2.minNode.key < h1.minNode.key) { - h.minNode = h2.minNode; - } - } - } else { - h.minNode = h2.minNode; - } - - h.nNodes = h1.nNodes + h2.nNodes; - } - - return h; - } - - // union - - /** - * Creates a String representation of this Fibonacci heap. - * - * @return String of this. - */ - public String toString() - { - if (minNode == null) { - return "FibonacciHeap=[]"; - } - - // create a new stack and put root on it - Stack> stack = new Stack>(); - stack.push(minNode); - - StringBuffer buf = new StringBuffer(512); - buf.append("FibonacciHeap=["); - - // do a simple breadth-first traversal on the tree - while (!stack.empty()) { - FibonacciHeapNode curr = stack.pop(); - buf.append(curr); - buf.append(", "); - - if (curr.child != null) { - stack.push(curr.child); - } - - FibonacciHeapNode start = curr; - curr = curr.right; - - while (curr != start) { - buf.append(curr); - buf.append(", "); - - if (curr.child != null) { - stack.push(curr.child); - } - - curr = curr.right; - } - } - - buf.append(']'); - - return buf.toString(); - } - - // toString - - /** - * Performs a cascading cut operation. This cuts y from its parent and then - * does the same for its parent, and so on up the tree. - * - *

    Running time: O(log n); O(1) excluding the recursion

    - * - * @param y node to perform cascading cut on - */ - protected void cascadingCut(FibonacciHeapNode y) - { - FibonacciHeapNode z = y.parent; - - // if there's a parent... - if (z != null) { - // if y is unmarked, set it marked - if (!y.mark) { - y.mark = true; - } else { - // it's marked, cut it from parent - cut(y, z); - - // cut its parent as well - cascadingCut(z); - } - } - } - - // cascadingCut - - protected void consolidate() - { - int arraySize = - ((int) Math.floor(Math.log(nNodes) * oneOverLogPhi)) + 1; - - List> array = - new ArrayList>(arraySize); - - // Initialize degree array - for (int i = 0; i < arraySize; i++) { - array.add(null); - } - - // Find the number of root nodes. - int numRoots = 0; - FibonacciHeapNode x = minNode; - - if (x != null) { - numRoots++; - x = x.right; - - while (x != minNode) { - numRoots++; - x = x.right; - } - } - - // For each node in root list do... - while (numRoots > 0) { - // Access this node's degree.. - int d = x.degree; - FibonacciHeapNode next = x.right; - - // ..and see if there's another of the same degree. - for (;;) { - FibonacciHeapNode y = array.get(d); - if (y == null) { - // Nope. - break; - } - - // There is, make one of the nodes a child of the other. - // Do this based on the key value. - if (x.key > y.key) { - FibonacciHeapNode temp = y; - y = x; - x = temp; - } - - // FibonacciHeapNode y disappears from root list. - link(y, x); - - // We've handled this degree, go to next one. - array.set(d, null); - d++; - } - - // Save this node for later when we might encounter another - // of the same degree. - array.set(d, x); - - // Move forward through list. - x = next; - numRoots--; - } - - // Set min to null (effectively losing the root list) and - // reconstruct the root list from the array entries in array[]. - minNode = null; - - for (int i = 0; i < arraySize; i++) { - FibonacciHeapNode y = array.get(i); - if (y == null) { - continue; - } - - // We've got a live one, add it to root list. - if (minNode != null) { - // First remove node from root list. - y.left.right = y.right; - y.right.left = y.left; - - // Now add to root list, again. - y.left = minNode; - y.right = minNode.right; - minNode.right = y; - y.right.left = y; - - // Check if this is a new min. - if (y.key < minNode.key) { - minNode = y; - } - } else { - minNode = y; - } - } - } - - // consolidate - - /** - * The reverse of the link operation: removes x from the child list of y. - * This method assumes that min is non-null. - * - *

    Running time: O(1)

    - * - * @param x child of y to be removed from y's child list - * @param y parent of x about to lose a child - */ - protected void cut(FibonacciHeapNode x, FibonacciHeapNode y) - { - // remove x from childlist of y and decrement degree[y] - x.left.right = x.right; - x.right.left = x.left; - y.degree--; - - // reset y.child if necessary - if (y.child == x) { - y.child = x.right; - } - - if (y.degree == 0) { - y.child = null; - } - - // add x to root list of heap - x.left = minNode; - x.right = minNode.right; - minNode.right = x; - x.right.left = x; - - // set parent[x] to nil - x.parent = null; - - // set mark[x] to false - x.mark = false; - } - - // cut - - /** - * Make node y a child of node x. - * - *

    Running time: O(1) actual

    - * - * @param y node to become child - * @param x node to become parent - */ - protected void link(FibonacciHeapNode y, FibonacciHeapNode x) - { - // remove y from root list of heap - y.left.right = y.right; - y.right.left = y.left; - - // make y a child of x - y.parent = x; - - if (x.child == null) { - x.child = y; - y.right = y; - y.left = y; - } else { - y.left = x.child; - y.right = x.child.right; - x.child.right = y; - y.right.left = y; - } - - // increase degree[x] - x.degree++; - - // set mark[y] false - y.mark = false; - } - - // link -} - -// FibonacciHeap diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeapNode.java b/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeapNode.java deleted file mode 100644 index 2a54150bfac..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/util/FibonacciHeapNode.java +++ /dev/null @@ -1,154 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (barak_naveh@users.sourceforge.net) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * FibonnaciHeapNode.java - * -------------------------- - * (C) Copyright 1999-2008, by Nathan Fiedler and Contributors. - * - * Original Author: Nathan Fiedler - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 03-Sept-2003 : Adapted from Nathan Fiedler (JVS); - * - * Name Date Description - * ---- ---- ----------- - * nf 08/31/97 Initial version - * nf 09/07/97 Removed FibHeapData interface - * nf 01/20/01 Added synchronization - * nf 01/21/01 Made Node an inner class - * nf 01/05/02 Added clear(), renamed empty() to - * isEmpty(), and renamed printHeap() - * to toString() - * nf 01/06/02 Removed all synchronization - * JVS 06/24/06 Generics - * - */ -package org.jgrapht.util; - -/** - * Implements a node of the Fibonacci heap. It holds the information necessary - * for maintaining the structure of the heap. It also holds the reference to the - * key value (which is used to determine the heap structure). - * - * @author Nathan Fiedler - */ -public class FibonacciHeapNode -{ - //~ Instance fields -------------------------------------------------------- - - /** - * Node data. - */ - T data; - - /** - * first child node - */ - FibonacciHeapNode child; - - /** - * left sibling node - */ - FibonacciHeapNode left; - - /** - * parent node - */ - FibonacciHeapNode parent; - - /** - * right sibling node - */ - FibonacciHeapNode right; - - /** - * true if this node has had a child removed since this node was added to - * its parent - */ - boolean mark; - - /** - * key value for this node - */ - double key; - - /** - * number of children of this node (does not count grandchildren) - */ - int degree; - - //~ Constructors ----------------------------------------------------------- - - /** - * Default constructor. Initializes the right and left pointers, making this - * a circular doubly-linked list. - * - * @param data data for this node - */ - public FibonacciHeapNode(T data) - { - right = this; - left = this; - this.data = data; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Obtain the key for this node. - * - * @return the key - */ - public final double getKey() - { - return key; - } - - /** - * Obtain the data for this node. - */ - public final T getData() - { - return data; - } - - /** - * Return the string representation of this object. - * - * @return string representing this object - */ - public String toString() - { - return Double.toString(key); - } - - // toString -} - -// End FibonacciHeapNode.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/LiveIterableWrapper.java b/jgrapht-core/src/main/java/org/jgrapht/util/LiveIterableWrapper.java new file mode 100644 index 00000000000..25075d3e4e2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/LiveIterableWrapper.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/MathUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/MathUtil.java index 87620d8d783..2b79e365f31 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/MathUtil.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/MathUtil.java @@ -1,61 +1,71 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * 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. - */ -/* ----------------- - * MathUtil.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Assaf Lehr - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; /** - * Math Utilities. Currently contains the following: - *
  • factorial(int N) - caclulate the factorial of N (aka N!) - * - * @author Assaf - * @since May 30, 2005 + * Math Utilities. + * + * @author Assaf Lehr */ public class MathUtil { - //~ Methods ---------------------------------------------------------------- - public static long factorial(int N) + /** + * 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++) { + for (int i = 1; i <= n; i++) { multi = multi * i; } return multi; } -} -// End MathUtil.java + /** + * 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/ModifiableInteger.java b/jgrapht-core/src/main/java/org/jgrapht/util/ModifiableInteger.java index 041c6aac190..0b879f591ff 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/ModifiableInteger.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/ModifiableInteger.java @@ -1,112 +1,80 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * ModifiableInteger.java - * ---------------------- - * - * (C) Copyright 2002-2004, by Barak Naveh and Contributors. +/* + * (C) Copyright 2002-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 2004-05-27 : Initial version (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * The {@code ModifiableInteger} class wraps a value of the primitive type {@code int} in + * an object, similarly to {@link java.lang.Integer}. An object of type + * {@code ModifiableInteger} contains a single field whose type is {@code 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.

    + *

    + * Unlike {@code java.lang.Integer}, the {@code 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 {@code 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.

    + *

    + * 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 {@code Map} and {@code Set}. + *

    * * @author Barak Naveh - * @since May 27, 2004 */ public class ModifiableInteger extends Number implements Comparable { - //~ Static fields/initializers --------------------------------------------- - private static final long serialVersionUID = 3618698612851422261L; - //~ Instance fields -------------------------------------------------------- - /** - * The int value represented by this ModifiableInteger. + * The int value represented by this {@code ModifiableInteger}. */ public int value; - //~ Constructors ----------------------------------------------------------- - /** - * !!! DON'T USE - Use the {@link #ModifiableInteger(int)} constructor - * instead !!! + * !!! 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.

    + *

    + * 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() + @Deprecated + public ModifiableInteger() { } /** - * Constructs a newly allocated ModifiableInteger object that - * represents the specified int value. + * Constructs a newly allocated {@code ModifiableInteger} object that represents the + * specified {@code int} value. * - * @param value the value to be represented by the - * ModifiableInteger object. + * @param value the value to be represented by the {@code ModifiableInteger} object. */ public ModifiableInteger(int value) { this.value = value; } - //~ Methods ---------------------------------------------------------------- - /** * Sets a new value for this modifiable integer. * @@ -118,9 +86,8 @@ public void setValue(int 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. + * 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. */ @@ -146,45 +113,46 @@ public void decrement() } /** - * Compares two ModifiableInteger objects numerically. + * Compares two {@code ModifiableInteger} objects numerically. * - * @param anotherInteger the ModifiableInteger to be compared. + * @param anotherInteger the {@code 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). - */ + * @return the value {@code 0} if this {@code ModifiableInteger} is equal to the + * argument {@code ModifiableInteger}; a value less than {@code 0} if this + * {@code ModifiableInteger} is numerically less than the argument + * {@code ModifiableInteger}; and a value greater than {@code 0} if this + * {@code ModifiableInteger} is numerically greater than the argument + * {@code ModifiableInteger} (signed comparison). + */ + @Override public int compareTo(ModifiableInteger anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; - return (thisVal < anotherVal) ? -1 : ((thisVal == anotherVal) ? 0 : 1); + 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. + * Compares this object to the specified object. The result is {@code + * true} if and only if the argument is not {@code null} and is an + * {@code ModifiableInteger} object that contains the same {@code + * int} value as this object. * * @param o the object to compare with. * - * @return true if the objects are the same; false - * otherwise. + * @return {@code true} if the objects are the same; {@code false} otherwise. */ + @Override public boolean equals(Object o) { if (o instanceof ModifiableInteger) { @@ -197,18 +165,19 @@ public boolean equals(Object o) /** * @see Number#floatValue() */ + @Override public float floatValue() { return this.value; } /** - * Returns a hash code for this ModifiableInteger. + * Returns a hash code for this {@code ModifiableInteger}. * - * @return a hash code value for this object, equal to the primitive - * int value represented by this ModifiableInteger - * object. + * @return a hash code value for this object, equal to the primitive {@code + * int} value represented by this {@code ModifiableInteger} object. */ + @Override public int hashCode() { return this.value; @@ -217,6 +186,7 @@ public int hashCode() /** * @see Number#intValue() */ + @Override public int intValue() { return this.value; @@ -225,37 +195,34 @@ public int intValue() /** * @see Number#longValue() */ + @Override public long longValue() { return this.value; } /** - * Returns an Integer object representing this - * ModifiableInteger's value. + * Returns an {@code Integer} object representing this {@code + * ModifiableInteger}'s value. * - * @return an Integer representation of the value of this - * object. + * @return an {@code Integer} representation of the value of this object. */ public Integer toInteger() { - return Integer.valueOf(this.value); + 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. + * Returns a {@code String} object representing this {@code + * 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. + * @return a string representation of the value of this object in base 10. */ + @Override public String toString() { return String.valueOf(this.value); } } - -// End ModifiableInteger.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/PrefetchIterator.java b/jgrapht-core/src/main/java/org/jgrapht/util/PrefetchIterator.java index e37fde09864..2dd64570ce5 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/PrefetchIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/PrefetchIterator.java @@ -1,98 +1,79 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * 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. - */ -/* ----------------- - * PrefetchIterator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Assaf Lehr - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * 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. + * Utility class to help implement an iterator/enumerator in which the {@link #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. + *

    + * Many classes which implement an iterator face a common problem: if there is no easy way to + * calculate {@link #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. + *

    + * 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 supllies 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();
    -        }
    -  }
    - * + *
    + * 
    + *  //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 + * @author Assaf Lehr */ public class PrefetchIterator - implements Iterator, - Enumeration + implements Iterator, Enumeration { - //~ Instance fields -------------------------------------------------------- - private NextElementFunctor innerEnum; private E getNextLastResult; private boolean isGetNextLastResultUpToDate = false; @@ -100,18 +81,19 @@ public class PrefetchIterator private boolean flagIsEnumerationStartedEmpty = true; private int innerFunctorUsageCounter = 0; - //~ Constructors ----------------------------------------------------------- - + /** + * Construct a new prefetch iterator. + * + * @param aEnum the next element functor + */ public PrefetchIterator(NextElementFunctor aEnum) { innerEnum = aEnum; } - //~ Methods ---------------------------------------------------------------- - /** - * Serves as one contact place to the functor; all must use it and not - * directly the NextElementFunctor. + * Serves as one contact place to the functor; all must use it and not directly the + * NextElementFunctor. */ private E getNextElementFromInnerFunctor() { @@ -125,13 +107,17 @@ private E getNextElementFromInnerFunctor() } /** - * 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!) + * {@inheritDoc} */ + @Override public E nextElement() { - E result = null; + /* + * 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 { @@ -143,11 +129,15 @@ public E nextElement() } /** - * If (isGetNextLastResultUpToDate==true) returns true else 1. calculates - * getNext() and saves it 2. sets isGetNextLastResultUpToDate to true. + * {@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; } @@ -167,19 +157,18 @@ public boolean hasMoreElements() } // 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)) + * Tests whether the enumeration started as an empty one. It does not matter if it + * {@link #hasMoreElements()} now, only at initialization time. + * + *

    Efficiency: if {@link #nextElement()}, {@link #hasMoreElements()} were never used, + * it activates the {@link #hasMoreElements()} once. Else it is immediately $(O(1))$ + * + * @return {@code true} if the enumeration started as an empty one, {@code false} otherwise. */ public boolean isEnumerationStartedEmpty() { if (this.innerFunctorUsageCounter == 0) { - if (hasMoreElements()) { - return false; - } else { - return true; - } + return !hasMoreElements(); } else // it is not the first time , so use the saved value // which was initilaizeed during a call to // getNextElementFromInnerFunctor @@ -188,36 +177,49 @@ public boolean isEnumerationStartedEmpty() } } + /** + * {@inheritDoc} + */ + @Override public boolean hasNext() { return this.hasMoreElements(); } + /** + * {@inheritDoc} + */ + @Override public E next() { return this.nextElement(); } /** - * Always throws UnsupportedOperationException. + * {@inheritDoc} */ + @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } - //~ Inner Interfaces ------------------------------------------------------- - + /** + * A functor for the calculation of the next element. + * + * @param the element type + */ public interface NextElementFunctor { /** - * You must implement that NoSuchElementException is thrown on - * nextElement() if it is out of bound. + * 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 */ - public EE nextElement() + EE nextElement() throws NoSuchElementException; } } - -// End PrefetchIterator.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/RadixSort.java b/jgrapht-core/src/main/java/org/jgrapht/util/RadixSort.java new file mode 100644 index 00000000000..a892bf5ac84 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/RadixSort.java @@ -0,0 +1,112 @@ +/* + * (C) Copyright 2018-2023, 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 +{ + + /** + * @deprecated use {@link #setCutOff(int)} instead + */ + @Deprecated(since = "1.5.2", forRemoval = true) + public static int CUT_OFF = 40; // @CS.suppress[StaticVariableName] + // TODO: make this static field private, rename it to "cutOff" to comply + // with checkstyle naming rules and remove the // @CS.supress comment and in jgrapht_checks.xml + // the rule SuppressWithNearbyCommentFilter + + public static void setCutOff(int cutOff) + { + CUT_OFF = cutOff; + } + + 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()]); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/SupplierException.java b/jgrapht-core/src/main/java/org/jgrapht/util/SupplierException.java new file mode 100644 index 00000000000..a458e26b79b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/SupplierException.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2021-2023, 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); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/SupplierUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/SupplierUtil.java new file mode 100644 index 00000000000..606a849fc1e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/SupplierUtil.java @@ -0,0 +1,252 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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()); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/TypeUtil.java b/jgrapht-core/src/main/java/org/jgrapht/util/TypeUtil.java index 81f29375f24..d08faa88825 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/TypeUtil.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/TypeUtil.java @@ -1,67 +1,42 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2006-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ----------------- - * TypeUtil.java - * ----------------- - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: John V. Sichi - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- - * 07-May-2006 : Initial version (JVS); + * 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. + * TypeUtil isolates type-unsafety so that code which uses it for legitimate reasons can stay + * warning-free. * * @author John V. Sichi */ -public class TypeUtil +public class TypeUtil { - //~ Methods ---------------------------------------------------------------- - /** * Casts an object to a type. * * @param o object to be cast - * @param typeDecl conveys the target type information; the actual value is - * unused and can be null since this is all just stupid compiler tricks + * @param the type of the result * * @return the result of the cast */ @SuppressWarnings("unchecked") - public static T uncheckedCast(Object o, TypeUtil typeDecl) + public static T uncheckedCast(Object o) { return (T) o; } -} -// End TypeUtil.java +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/UnmodifiableUnionSet.java b/jgrapht-core/src/main/java/org/jgrapht/util/UnmodifiableUnionSet.java new file mode 100644 index 00000000000..20e15c0fb61 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/UnmodifiableUnionSet.java @@ -0,0 +1,176 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + } + } + } + + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/VertexPair.java b/jgrapht-core/src/main/java/org/jgrapht/util/VertexPair.java deleted file mode 100644 index 5dc1c5ae837..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/util/VertexPair.java +++ /dev/null @@ -1,132 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * VertexPair.java - * ------------------------- - * (C) Copyright 2009-2009, by Soren Davidsen and Contributors - * - * Original Author: Soren Davidsen - * - * $Id$ - * - * Changes - * ------- - * 03-Dec-2009 : Initial revision (SD); - * - */ -package org.jgrapht.util; - -/** - * Representation of a pair of vertices; to be replaced by Pair if Sun ever - * gets around to adding Pair to java.util. - * - * @author Soren - */ -public class VertexPair -{ - //~ Instance fields -------------------------------------------------------- - - private V n1; - private V n2; - - //~ Constructors ----------------------------------------------------------- - - public VertexPair(V n1, V n2) - { - this.n1 = n1; - this.n2 = n2; - } - - //~ Methods ---------------------------------------------------------------- - - public V getFirst() - { - return n1; - } - - public V getSecond() - { - return n2; - } - - /** - * Assess if this pair contains the vertex. - * - * @param v The vertex in question - * - * @return true if contains, false otherwise - */ - public boolean hasVertex(V v) - { - return v.equals(n1) || v.equals(n2); - } - - public V getOther(V one) - { - if (one.equals(n1)) { - return n2; - } else if (one.equals(n2)) { - return n1; - } else { - return null; - } - } - - @Override public String toString() - { - return n1 + "," + n2; - } - - @Override public boolean equals(Object o) - { - if (this == o) { - return true; - } - if ((o == null) || (getClass() != o.getClass())) { - return false; - } - - @SuppressWarnings("unchecked") - VertexPair that = (VertexPair) o; - - if ((n1 != null) ? (!n1.equals(that.n1)) : (that.n1 != null)) { - return false; - } - if ((n2 != null) ? (!n2.equals(that.n2)) : (that.n2 != null)) { - return false; - } - - return true; - } - - @Override public int hashCode() - { - int result = (n1 != null) ? n1.hashCode() : 0; - result = (31 * result) + ((n2 != null) ? n2.hashCode() : 0); - return result; - } -} - -// End VertexPair.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/VertexToIntegerMapping.java b/jgrapht-core/src/main/java/org/jgrapht/util/VertexToIntegerMapping.java new file mode 100644 index 00000000000..20ee32292c6 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/VertexToIntegerMapping.java @@ -0,0 +1,97 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/WeightCombiner.java b/jgrapht-core/src/main/java/org/jgrapht/util/WeightCombiner.java index 637dd18a6ad..59ded04b2b8 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/util/WeightCombiner.java +++ b/jgrapht-core/src/main/java/org/jgrapht/util/WeightCombiner.java @@ -1,106 +1,59 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2009-2023, by Ilya Razenshteyn and Contributors. * - * 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. - */ -/* ------------------------- - * WeightCombiner.java - * ------------------------- - * (C) Copyright 2009-2009, by Ilya Razenshteyn - * - * Original Author: Ilya Razenshteyn and Contributors. + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 02-Feb-2009 : Initial revision (IR); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. */ +@FunctionalInterface public interface WeightCombiner { - //~ Instance fields -------------------------------------------------------- - /** * Sum of weights. */ - public WeightCombiner SUM = - new WeightCombiner() { - public double combine(double a, double b) - { - return a + b; - } - }; + WeightCombiner SUM = (WeightCombiner & Serializable) (a, b) -> a + b; + + /** + * Multiplication of weights. + */ + WeightCombiner MULT = (WeightCombiner & Serializable) (a, b) -> a * b; /** * Minimum weight. */ - public WeightCombiner MIN = - new WeightCombiner() { - public double combine(double a, double b) - { - return Math.min(a, b); - } - }; + WeightCombiner MIN = (WeightCombiner & Serializable) Math::min; /** * Maximum weight. */ - public WeightCombiner MAX = - new WeightCombiner() { - public double combine(double a, double b) - { - return Math.max(a, b); - } - }; + WeightCombiner MAX = (WeightCombiner & Serializable) Math::max; /** * First weight. */ - public WeightCombiner FIRST = - new WeightCombiner() { - public double combine(double a, double b) - { - return a; - } - }; + WeightCombiner FIRST = (WeightCombiner & Serializable) (a, b) -> a; /** * Second weight. */ - public WeightCombiner SECOND = - new WeightCombiner() { - public double combine(double a, double b) - { - return b; - } - }; - - //~ Methods ---------------------------------------------------------------- + WeightCombiner SECOND = (WeightCombiner & Serializable) (a, b) -> b; /** * Combines two weights. @@ -112,5 +65,3 @@ public double combine(double a, double b) */ double combine(double a, double b); } - -// End WeightCombiner.java diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/WeightedUnmodifiableSet.java b/jgrapht-core/src/main/java/org/jgrapht/util/WeightedUnmodifiableSet.java new file mode 100644 index 00000000000..3aedfe23d4c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/WeightedUnmodifiableSet.java @@ -0,0 +1,171 @@ +/* + * (C) Copyright 2018-2023, 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(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/util/package-info.java new file mode 100644 index 00000000000..6ad3f96d9cd --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/util/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Non-graph-specific data structures, algorithms, and utilities used by JGraphT. + */ +package org.jgrapht.util; diff --git a/jgrapht-core/src/main/java/org/jgrapht/util/package.html b/jgrapht-core/src/main/java/org/jgrapht/util/package.html deleted file mode 100644 index 15706d5dd14..00000000000 --- a/jgrapht-core/src/main/java/org/jgrapht/util/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -Non-graph-specific data structures, algorithms, and utilities used by -JGraphT. - - diff --git a/jgrapht-core/src/main/java/overview.html b/jgrapht-core/src/main/java/overview.html index e7bc7bfcdcb..8278c491e84 100644 --- a/jgrapht-core/src/main/java/overview.html +++ b/jgrapht-core/src/main/java/overview.html @@ -6,7 +6,7 @@ rich gallery of graphs and is designed to be powerful, extensible and easy to use.

    Visit http://jgrapht.sourceforge.net +href="http://www.jgrapht.org" target="_top">http://www.jgrapht.org to download and to get the latest info on JGraphT. - \ No newline at end of file + diff --git a/jgrapht-core/src/main/resources/META-INF/MANIFEST.MF b/jgrapht-core/src/main/resources/META-INF/MANIFEST.MF deleted file mode 100644 index 7cedfe56d49..00000000000 --- a/jgrapht-core/src/main/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,29 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: JGraphT Plugin -Bundle-SymbolicName: org.jgrapht;singleton:=true -Bundle-Version: 0.8.4.dev -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 -Bundle-ClassPath: ., - lib/xmlunit1.0.jar, - lib/jgraph.jar, - lib/TGGraphLayout.jar, - lib/junit.jar -Export-Package: org.jgrapht, - org.jgrapht.alg, - org.jgrapht.alg.util, - org.jgrapht.demo, - org.jgrapht.event, - org.jgrapht.experimental, - org.jgrapht.experimental.alg, - org.jgrapht.experimental.alg.color, - org.jgrapht.experimental.equivalence, - org.jgrapht.experimental.isomorphism, - org.jgrapht.experimental.permutation, - org.jgrapht.experimental.touchgraph, - org.jgrapht.ext, - org.jgrapht.generate, - org.jgrapht.graph, - org.jgrapht.traverse, - org.jgrapht.util -Bundle-Vendor: Barak Naveh, John V. Sichi and contributors (see http://sourceforge.net/projects/jgrapht/) diff --git a/jgrapht-core/src/test/java/org/jgrapht/AllTests.java b/jgrapht-core/src/test/java/org/jgrapht/AllTests.java deleted file mode 100644 index 4f5d555f306..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/AllTests.java +++ /dev/null @@ -1,125 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------- - * AllTests.java - * ------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * - */ -package org.jgrapht; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.alg.*; -import org.jgrapht.alg.util.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; -import org.jgrapht.traverse.*; -import org.jgrapht.util.*; - - -/** - * Runs all unit tests of the JGraphT library. - * - * @author Barak Naveh - */ -public final class AllTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite that includes all JGraphT tests. - * - * @return a test suite that includes all JGraphT tests. - */ - public static Test suite() - { - ExpandableTestSuite suite = - new ExpandableTestSuite("All tests of JGraphT"); - - suite.addTestSuit((TestSuite) AllAlgTests.suite()); - suite.addTestSuit((TestSuite) AllAlgUtilTests.suite()); - suite.addTestSuit((TestSuite) AllGenerateTests.suite()); - suite.addTestSuit((TestSuite) AllGraphTests.suite()); - suite.addTestSuit((TestSuite) AllTraverseTests.suite()); - suite.addTestSuit((TestSuite) AllUtilTests.suite()); - - return suite; - } - - //~ Inner Classes ---------------------------------------------------------- - - private static class ExpandableTestSuite - extends TestSuite - { - /** - * @see TestSuite#TestSuite() - */ - public ExpandableTestSuite() - { - super(); - } - - /** - * @see TestSuite#TestSuite(java.lang.String) - */ - public ExpandableTestSuite(String name) - { - super(name); - } - - /** - * Adds all the test from the specified suite into this suite. - * - * @param suite - */ - public void addTestSuit(TestSuite suite) - { - for (Enumeration e = suite.tests(); e.hasMoreElements();) { - Test t = (Test) e.nextElement(); - this.addTest(t); - } - } - } -} - -// End AllTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/EnhancedTestCase.java b/jgrapht-core/src/test/java/org/jgrapht/EnhancedTestCase.java deleted file mode 100644 index 729b64b5166..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/EnhancedTestCase.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------- - * EnhancedTestCase.java - * --------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * - */ -package org.jgrapht; - -import junit.framework.*; - - -/** - * A little extension to JUnit's TestCase. - * - * @author Barak Naveh - * @since Jul 25, 2003 - */ -public abstract class EnhancedTestCase - extends TestCase -{ - //~ Constructors ----------------------------------------------------------- - - /** - * @see TestCase#TestCase() - */ - public EnhancedTestCase() - { - super(); - } - - /** - * @see TestCase#TestCase(java.lang.String) - */ - public EnhancedTestCase(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * It means: it's wrong that we got here. - */ - public void assertFalse() - { - assertTrue(false); - } - - /** - * It means: it's right that we got here. - */ - public void assertTrue() - { - assertTrue(true); - } -} - -// End EnhancedTestCase.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/FastTestSuite.java b/jgrapht-core/src/test/java/org/jgrapht/FastTestSuite.java new file mode 100644 index 00000000000..3f78024deab --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/FastTestSuite.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2018-2023, 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 org.junit.platform.suite.api.*; + +/** + * Suite of fast unit tests only (as run by mvn test). + * + * @author John Sichi + */ +@ExcludePackages({"org.jgrapht.perf"}) +@ExcludeTags({ "slow", "optional" }) +@SelectPackages({"org.jgrapht"}) +@Suite +public class FastTestSuite +{ +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/GraphMetricsTest.java b/jgrapht-core/src/test/java/org/jgrapht/GraphMetricsTest.java new file mode 100644 index 00000000000..999a034d5c6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/GraphMetricsTest.java @@ -0,0 +1,423 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.Assertions.*; + +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.jupiter.api.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 numTests = 300; + Random random = new Random(0x88_88); + + for (int test = 0; test < numTests; 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); + + assertEquals(naiveCountTriangles(graph), GraphMetrics.getNumberOfTriangles(graph)); + } + } + + @Test + public void testCountTriangles2() + { + final int numTests = 100; + Random random = new Random(0x88_88); + + for (int test = 0; test < numTests; 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); + + 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); + + assertEquals(50 * 49 * 48 / 6, GraphMetrics.getNumberOfTriangles(graph)); + 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); + + assertEquals(49, GraphMetrics.getNumberOfTriangles(graph)); + assertEquals(49, naiveCountTriangles(graph)); + + // Named graphs + + NamedGraphGenerator gen = new NamedGraphGenerator<>(); + + graph.removeAllVertices(new HashSet<>(graph.vertexSet())); + gen.generatePetersenGraph(graph); + + assertEquals(0, GraphMetrics.getNumberOfTriangles(graph)); + assertEquals(0, naiveCountTriangles(graph)); + + graph.removeAllVertices(new HashSet<>(graph.vertexSet())); + gen.generateDiamondGraph(graph); + + assertEquals(2, GraphMetrics.getNumberOfTriangles(graph)); + assertEquals(2, naiveCountTriangles(graph)); + + graph.removeAllVertices(new HashSet<>(graph.vertexSet())); + gen.generateGoldnerHararyGraph(graph); + + assertEquals(25, GraphMetrics.getNumberOfTriangles(graph)); + assertEquals(25, naiveCountTriangles(graph)); + + graph.removeAllVertices(new HashSet<>(graph.vertexSet())); + gen.generateKlein7RegularGraph(graph); + + assertEquals(56, GraphMetrics.getNumberOfTriangles(graph)); + 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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/GraphTestsTest.java b/jgrapht-core/src/test/java/org/jgrapht/GraphTestsTest.java new file mode 100644 index 00000000000..9ca281430ad --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/GraphTestsTest.java @@ -0,0 +1,567 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + 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); + assertFalse(GraphTests.hasSelfLoops(g1)); + + Graph g2 = new SimpleGraph<>(DefaultEdge.class); + assertFalse(GraphTests.hasSelfLoops(g2)); + + Graph g3 = new DirectedPseudograph<>(DefaultEdge.class); + assertFalse(GraphTests.hasSelfLoops(g3)); + + Graphs.addAllVertices(g3, Arrays.asList(1, 2)); + g3.addEdge(1, 2); + g3.addEdge(2, 1); + assertFalse(GraphTests.hasSelfLoops(g3)); + g3.addEdge(2, 2); + 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); + assertFalse(GraphTests.hasSelfLoops(g4)); + g4.addEdge(2, 2); + assertTrue(GraphTests.hasSelfLoops(g4)); + } + + @Test + public void testHasMultipleEdges() + { + Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); + assertFalse(GraphTests.hasMultipleEdges(g1)); + + Graph g2 = new SimpleGraph<>(DefaultEdge.class); + assertFalse(GraphTests.hasMultipleEdges(g2)); + + Graph g3 = new DirectedPseudograph<>(DefaultEdge.class); + assertFalse(GraphTests.hasMultipleEdges(g3)); + Graphs.addAllVertices(g3, Arrays.asList(1, 2)); + g3.addEdge(1, 2); + g3.addEdge(2, 1); + g3.addEdge(1, 1); + assertFalse(GraphTests.hasMultipleEdges(g3)); + g3.addEdge(2, 2); + assertFalse(GraphTests.hasMultipleEdges(g3)); + g3.addEdge(2, 1); + 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); + assertFalse(GraphTests.hasMultipleEdges(g4)); + g4.addEdge(2, 1); + 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); + assertFalse(GraphTests.hasMultipleEdges(g5)); + g5.addEdge(1, 1); + 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/GraphTestsUtils.java b/jgrapht-core/src/test/java/org/jgrapht/GraphTestsUtils.java new file mode 100644 index 00000000000..f8a7f4d93e1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/GraphTestsUtils.java @@ -0,0 +1,53 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/GraphsTest.java b/jgrapht-core/src/test/java/org/jgrapht/GraphsTest.java new file mode 100644 index 00000000000..2632b83abe3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/GraphsTest.java @@ -0,0 +1,451 @@ +/* + * (C) Copyright 2003-2023, 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 static org.junit.jupiter.api.Assertions.*; + +import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +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); + + assertEquals(expectedGraph, graph); + 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); + + assertEquals(expectedGraph, graph); + 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); + + assertEquals(expectedGraph, graph); + 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); + + assertEquals(expectedGraph, graph); + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + assertEquals(Set.of(2, 4), neighborSet); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/IntegrationTestSuite.java b/jgrapht-core/src/test/java/org/jgrapht/IntegrationTestSuite.java new file mode 100644 index 00000000000..e0baa5add14 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/IntegrationTestSuite.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2018-2023, 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 org.junit.platform.suite.api.*; + +/** + * Suite of all unit and integration tests (as run by mvn verify). Excludes performance tests and + * optional tests. + * + * @author John Sichi + */ +@ExcludePackages({"org.jgrapht.perf"}) +@ExcludeTags({ "optional" }) +@SelectPackages({"org.jgrapht"}) +@Suite +public class IntegrationTestSuite +{ +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/PerformanceTestSuite.java b/jgrapht-core/src/test/java/org/jgrapht/PerformanceTestSuite.java new file mode 100644 index 00000000000..82bf44e28b6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/PerformanceTestSuite.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2018-2023, 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 org.junit.platform.suite.api.*; + +/** + * Suite of performance tests only. + * + * @author John Sichi + */ +@SelectPackages({"org.jgrapht.perf"}) +@Suite +public class PerformanceTestSuite +{ +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/TestUtil.java b/jgrapht-core/src/test/java/org/jgrapht/TestUtil.java new file mode 100644 index 00000000000..93460ab12c8 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/TestUtil.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2020-2023, 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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/AllAlgTests.java b/jgrapht-core/src/test/java/org/jgrapht/alg/AllAlgTests.java deleted file mode 100644 index 816f135456d..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/AllAlgTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * AllAlgTests.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * - */ -package org.jgrapht.alg; - -import junit.framework.*; - -import org.jgrapht.experimental.isomorphism.*; - - -/** - * A TestSuite for all tests in this package. - * - * @author Barak Naveh - */ -public final class AllAlgTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllAlgTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite for all tests in this package. - * - * @return a test suite for all tests in this package. - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // $JUnit-BEGIN$ - suite.addTest(new TestSuite(ConnectivityInspectorTest.class)); - suite.addTest(new TestSuite(DijkstraShortestPathTest.class)); - suite.addTest(new TestSuite(BellmanFordShortestPathTest.class)); - suite.addTest(new TestSuite(FloydWarshallShortestPathsTest.class)); - suite.addTest(new TestSuite(VertexCoversTest.class)); - suite.addTest(new TestSuite(CycleDetectorTest.class)); - suite.addTest(new TestSuite(BronKerboschCliqueFinderTest.class)); - suite.addTest(new TestSuite(TransitiveClosureTest.class)); - suite.addTest(new TestSuite(BiconnectivityInspectorTest.class)); - suite.addTest(new TestSuite(BlockCutpointGraphTest.class)); - suite.addTest(new TestSuite(KShortestPathCostTest.class)); - suite.addTest(new TestSuite(KShortestPathKValuesTest.class)); - suite.addTest(new TestSuite(KSPExampleTest.class)); - suite.addTest(new TestSuite(KSPDiscardsValidPathsTest.class)); - suite.addTestSuite(IsomorphismInspectorTest.class); - suite.addTest(new TestSuite(EdmondsKarpMaximumFlowTest.class)); - suite.addTest(new TestSuite(ChromaticNumberTest.class)); - suite.addTest(new TestSuite(EulerianCircuitTest.class)); - suite.addTest(new TestSuite(HamiltonianCycleTest.class)); - suite.addTest(new TestSuite(KruskalMinimumSpanningTreeTest.class)); - suite.addTest(new TestSuite(StoerWagnerMinimumCutTest.class)); - suite.addTest(new TestSuite(EdmondsBlossomShrinkingTest.class)); - - // $JUnit-END$ - return suite; - } -} - -// End AllAlgTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/BellmanFordShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/BellmanFordShortestPathTest.java deleted file mode 100644 index 51c27335fe1..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/BellmanFordShortestPathTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * BellmanFordShortestPathTest.java - * ------------------------------ - * (C) Copyright 2006-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 14-Jan-2006 : Initial revision (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public class BellmanFordShortestPathTest - extends ShortestPathTestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testConstructor() - { - BellmanFordShortestPath path; - Graph g = create(); - - path = new BellmanFordShortestPath(g, V3); - - // find best path with no constraint on number of hops - assertEquals( - Arrays.asList( - new DefaultEdge[] { - e13, - e12, - e24, - e45 - }), - path.getPathEdgeList(V5)); - assertEquals(15.0, path.getCost(V5), 0); - - // find best path within 2 hops (less than optimal) - path = - new BellmanFordShortestPath( - g, - V3, - 2); - assertEquals( - Arrays.asList( - new DefaultEdge[] { - e34, - e45 - }), - path.getPathEdgeList(V5)); - assertEquals(25.0, path.getCost(V5), 0); - - // find best path within 1 hop (doesn't exist!) - path = - new BellmanFordShortestPath( - g, - V3, - 1); - assertNull(path.getPathEdgeList(V5)); - assertEquals(Double.POSITIVE_INFINITY, path.getCost(V5)); - } - - protected List findPathBetween( - Graph g, - String src, - String dest) - { - return BellmanFordShortestPath.findPathBetween(g, src, dest); - } - - public void testWithNegativeEdges() - { - Graph g = createWithBias(true); - - List path; - - path = findPathBetween(g, V1, V4); - assertEquals(Arrays.asList( - new DefaultEdge[] { - e13, - e34 - }), path); - - path = findPathBetween(g, V1, V5); - assertEquals(Arrays.asList(new DefaultEdge[] { e15 }), path); - } -} - -// End BellmanFordShortestPathTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectedGraph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectedGraph.java deleted file mode 100644 index 990135b8fac..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectedGraph.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BiconnectedGraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -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"); - } -} - -// End BiconnectedGraph.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectivityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectivityInspectorTest.java deleted file mode 100644 index f0ceceb0b89..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/BiconnectivityInspectorTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BiconnectivityInspectorTest.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class BiconnectivityInspectorTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testBiconnected() - { - BiconnectedGraph graph = new BiconnectedGraph(); - - BiconnectivityInspector inspector = new BiconnectivityInspector(graph); - - assertTrue(inspector.isBiconnected()); - assertEquals(0, inspector.getCutpoints().size()); - assertEquals(1, inspector.getBiconnectedVertexComponents().size()); - } - - public void testLinearGraph() - { - testLinearGraph(3); - testLinearGraph(5); - } - - public void testLinearGraph(int nbVertices) - { - UndirectedGraph graph = new SimpleGraph(DefaultEdge.class); - - LinearGraphGenerator generator = new LinearGraphGenerator(nbVertices); - generator.generateGraph( - graph, - new ClassBasedVertexFactory( - Object.class), - null); - - BiconnectivityInspector inspector = new BiconnectivityInspector(graph); - - assertEquals(nbVertices - 2, inspector.getCutpoints().size()); - assertEquals( - nbVertices - 1, - inspector.getBiconnectedVertexComponents().size()); - } - - public void testNotBiconnected() - { - NotBiconnectedGraph graph = new NotBiconnectedGraph(); - - BiconnectivityInspector inspector = new BiconnectivityInspector(graph); - - assertEquals(2, inspector.getCutpoints().size()); - assertEquals(3, inspector.getBiconnectedVertexComponents().size()); - } -} - -// End BiconnectivityInspectorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/BlockCutpointGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/BlockCutpointGraphTest.java deleted file mode 100644 index 7e3ffaa8057..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/BlockCutpointGraphTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * BlockCutpointGraphTest.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class BlockCutpointGraphTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testBiconnected() - { - BiconnectedGraph graph = new BiconnectedGraph(); - - BlockCutpointGraph blockCutpointGraph = new BlockCutpointGraph(graph); - testGetBlock(blockCutpointGraph); - - assertEquals(0, blockCutpointGraph.getCutpoints().size()); - int nbBiconnectedComponents = - blockCutpointGraph.vertexSet().size() - - blockCutpointGraph.getCutpoints().size(); - assertEquals(1, nbBiconnectedComponents); - } - - public void testGetBlock(BlockCutpointGraph blockCutpointGraph) - { - for ( - Iterator iter = blockCutpointGraph.vertexSet().iterator(); - iter.hasNext();) - { - UndirectedGraph component = (UndirectedGraph) iter.next(); - if (!component.edgeSet().isEmpty()) { - for ( - Iterator iterator = component.vertexSet().iterator(); - iterator.hasNext();) - { - Object vertex = iterator.next(); - if (!blockCutpointGraph.getCutpoints().contains(vertex)) { - assertEquals( - component, - blockCutpointGraph.getBlock(vertex)); - } - } - } else { - assertTrue( - blockCutpointGraph.getCutpoints().contains( - component.vertexSet().iterator().next())); - } - } - } - - public void testLinearGraph() - { - testLinearGraph(3); - testLinearGraph(5); - } - - public void testLinearGraph(int nbVertices) - { - UndirectedGraph graph = new SimpleGraph(DefaultEdge.class); - - LinearGraphGenerator generator = new LinearGraphGenerator(nbVertices); - generator.generateGraph( - graph, - new ClassBasedVertexFactory( - Object.class), - null); - - BlockCutpointGraph blockCutpointGraph = new BlockCutpointGraph(graph); - testGetBlock(blockCutpointGraph); - - assertEquals(nbVertices - 2, blockCutpointGraph.getCutpoints().size()); - int nbBiconnectedComponents = - blockCutpointGraph.vertexSet().size() - - blockCutpointGraph.getCutpoints().size(); - assertEquals(nbVertices - 1, nbBiconnectedComponents); - } - - public void testNotBiconnected() - { - UndirectedGraph graph = new NotBiconnectedGraph(); - - BlockCutpointGraph blockCutpointGraph = new BlockCutpointGraph(graph); - testGetBlock(blockCutpointGraph); - - assertEquals(2, blockCutpointGraph.getCutpoints().size()); - int nbBiconnectedComponents = - blockCutpointGraph.vertexSet().size() - - blockCutpointGraph.getCutpoints().size(); - assertEquals(3, nbBiconnectedComponents); - } -} - -// End BlockCutpointGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/BronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/BronKerboschCliqueFinderTest.java deleted file mode 100644 index 79b012539d7..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/BronKerboschCliqueFinderTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * BronKerboschCliqueFinderTest.java - * ------------------------------ - * (C) Copyright 2005-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 26-July-2005 : Initial revision (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public class BronKerboschCliqueFinderTest - extends TestCase -{ - //~ 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"; - - //~ 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.addVertex(V8); - - // 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); - } - - public void testFindBiggest() - { - SimpleGraph g = - new SimpleGraph(DefaultEdge.class); - createGraph(g); - - BronKerboschCliqueFinder finder = - new BronKerboschCliqueFinder(g); - - Collection> cliques = finder.getBiggestMaximalCliques(); - - assertEquals(1, cliques.size()); - - Set expected = new HashSet(); - expected.add(V1); - expected.add(V2); - expected.add(V3); - expected.add(V4); - - Set actual = cliques.iterator().next(); - - assertEquals(expected, actual); - } - - public void testFindAll() - { - SimpleGraph g = - new SimpleGraph(DefaultEdge.class); - createGraph(g); - - BronKerboschCliqueFinder finder = - new BronKerboschCliqueFinder(g); - - Collection> cliques = finder.getAllMaximalCliques(); - - assertEquals(4, 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); - - // convert result from Collection to Set because we don't want - // order to be significant - Set> actual = new HashSet>(cliques); - - assertEquals(expected, actual); - } -} - -// End BronKerboschCliqueFinderTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/ChromaticNumberTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/ChromaticNumberTest.java deleted file mode 100644 index 16c61089319..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/ChromaticNumberTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * ChromaticNumberTest.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Andrew Newell - */ -public class ChromaticNumberTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testChromaticNumber() - { - UndirectedGraph completeGraph = - new SimpleGraph( - DefaultEdge.class); - CompleteGraphGenerator completeGenerator = - new CompleteGraphGenerator( - 7); - completeGenerator.generateGraph( - completeGraph, - new ClassBasedVertexFactory(Object.class), - null); - - // A complete graph has a chromatic number equal to its order - assertEquals( - 7, - ChromaticNumber.findGreedyChromaticNumber(completeGraph)); - Map> coloring = - ChromaticNumber.findGreedyColoredGroups(completeGraph); - assertEquals( - 7, - coloring.keySet().size()); - - UndirectedGraph linearGraph = - new SimpleGraph( - DefaultEdge.class); - LinearGraphGenerator linearGenerator = - new LinearGraphGenerator( - 50); - linearGenerator.generateGraph( - linearGraph, - new ClassBasedVertexFactory(Object.class), - null); - - // A linear graph is a tree, and a greedy algorithm for chromatic number - // can always find a 2-coloring - assertEquals(2, ChromaticNumber.findGreedyChromaticNumber(linearGraph)); - } -} - -// End ChromaticNumberTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/ConnectivityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/ConnectivityInspectorTest.java deleted file mode 100644 index 3ba3ceded0a..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/ConnectivityInspectorTest.java +++ /dev/null @@ -1,377 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * ConnectivityInspectorTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 07-Aug-2003 : Initial revision (BN); - * 20-Apr-2005 : Added StrongConnectivityInspector test (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Barak Naveh - */ -public class ConnectivityInspectorTest - extends TestCase -{ - //~ 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; - } - - /** - * . - */ - public void testDirectedGraph() - { - ListenableDirectedGraph g = - new ListenableDirectedGraph( - 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.isGraphConnected()); - - g.addEdge(V1, V3); - - assertEquals(true, inspector.isGraphConnected()); - } - - /** - * . - */ - public void testIsGraphConnected() - { - Pseudograph g = create(); - ConnectivityInspector inspector = - new ConnectivityInspector(g); - - assertEquals(false, inspector.isGraphConnected()); - - g.removeVertex(V4); - inspector = new ConnectivityInspector(g); - assertEquals(true, inspector.isGraphConnected()); - - 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.isGraphConnected()); - } - - /** - * . - */ - public void testStronglyConnected1() - { - DirectedGraph g = - new DefaultDirectedGraph( - DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addVertex(V3); - g.addVertex(V4); - - g.addEdge(V1, V2); - g.addEdge(V2, V1); // strongly connected - - g.addEdge(V3, V4); // only weakly connected - - StrongConnectivityInspector inspector = - new StrongConnectivityInspector(g); - - // convert from List to Set because we need to ignore order - // during comparison - Set> actualSets = - new HashSet>(inspector.stronglyConnectedSets()); - - // construct the expected answer - Set> expectedSets = new HashSet>(); - Set set = new HashSet(); - set.add(V1); - set.add(V2); - expectedSets.add(set); - set = new HashSet(); - set.add(V3); - expectedSets.add(set); - set = new HashSet(); - set.add(V4); - expectedSets.add(set); - - assertEquals(expectedSets, actualSets); - - actualSets.clear(); - - List> subgraphs = - inspector.stronglyConnectedSubgraphs(); - for (DirectedSubgraph sg : subgraphs) { - actualSets.add(sg.vertexSet()); - - StrongConnectivityInspector ci = - new StrongConnectivityInspector(sg); - assertTrue(ci.isStronglyConnected()); - } - - assertEquals(expectedSets, actualSets); - } - - /** - * . - */ - public void testStronglyConnected2() - { - DirectedGraph g = - new DefaultDirectedGraph( - DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addVertex(V3); - g.addVertex(V4); - - g.addEdge(V1, V2); - g.addEdge(V2, V1); // strongly connected - - g.addEdge(V4, V3); // only weakly connected - g.addEdge(V3, V2); // only weakly connected - - StrongConnectivityInspector inspector = - new StrongConnectivityInspector(g); - - // convert from List to Set because we need to ignore order - // during comparison - Set> actualSets = - new HashSet>(inspector.stronglyConnectedSets()); - - // construct the expected answer - Set> expectedSets = new HashSet>(); - Set set = new HashSet(); - set.add(V1); - set.add(V2); - expectedSets.add(set); - set = new HashSet(); - set.add(V3); - expectedSets.add(set); - set = new HashSet(); - set.add(V4); - expectedSets.add(set); - - assertEquals(expectedSets, actualSets); - - actualSets.clear(); - - List> subgraphs = - inspector.stronglyConnectedSubgraphs(); - for (DirectedSubgraph sg : subgraphs) { - actualSets.add(sg.vertexSet()); - - StrongConnectivityInspector ci = - new StrongConnectivityInspector(sg); - assertTrue(ci.isStronglyConnected()); - } - - assertEquals(expectedSets, actualSets); - } - - /** - * . - */ - public void testStronglyConnected3() - { - DirectedGraph g = - new DefaultDirectedGraph( - 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); // strongly connected - - g.addEdge(V1, V4); - g.addEdge(V2, V4); - g.addEdge(V3, V4); // weakly connected - - StrongConnectivityInspector inspector = - new StrongConnectivityInspector(g); - - // convert from List to Set because we need to ignore order - // during comparison - Set> actualSets = - new HashSet>(inspector.stronglyConnectedSets()); - - // construct the expected answer - Set> expectedSets = new HashSet>(); - Set set = new HashSet(); - set.add(V1); - set.add(V2); - set.add(V3); - expectedSets.add(set); - set = new HashSet(); - set.add(V4); - expectedSets.add(set); - - assertEquals(expectedSets, actualSets); - - actualSets.clear(); - - List> subgraphs = - inspector.stronglyConnectedSubgraphs(); - - for (DirectedSubgraph sg : subgraphs) { - actualSets.add(sg.vertexSet()); - - StrongConnectivityInspector ci = - new StrongConnectivityInspector(sg); - assertTrue(ci.isStronglyConnected()); - } - - assertEquals(expectedSets, actualSets); - } - - public void testStronglyConnected4() - { - DefaultDirectedGraph graph = - new DefaultDirectedGraph( - new EdgeFactory() { - public String createEdge(Integer from, Integer to) - { - return (from + "->" + to).intern(); - } - }); - - new RingGraphGenerator(3).generateGraph( - graph, - new VertexFactory() { - private int i = 0; - - public Integer createVertex() - { - return i++; - } - }, - null); - - StrongConnectivityInspector sc = - new StrongConnectivityInspector( - graph); - Set> expected = new HashSet>(); - expected.add(graph.vertexSet()); - assertEquals( - expected, - new HashSet>(sc.stronglyConnectedSets())); - } -} - -// End ConnectivityInspectorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/CycleDetectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/CycleDetectorTest.java deleted file mode 100644 index 84d41693c09..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/CycleDetectorTest.java +++ /dev/null @@ -1,237 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * CycleDetectorTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): Khanh Vu - * - * $Id$ - * - * Changes - * ------- - * 16-Sept-2004 : Initial revision (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public class CycleDetectorTest - extends TestCase -{ - //~ 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); - } - - /** - * . - */ - public void testDirectedWithCycle() - { - DirectedGraph 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); - } - - /** - * . - */ - public void testDirectedWithDoubledCycle() - { - DirectedGraph 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") - public void testDirectedWithoutCycle() - { - DirectedGraph 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( - DirectedGraph g, - Set cyclicSet, - Set acyclicSet) - { - CycleDetector detector = - new CycleDetector(g); - - Set emptySet = Collections.EMPTY_SET; - - 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)); - } - } - - public void testVertexEquals() - { - DefaultDirectedGraph graph = - new DefaultDirectedGraph(DefaultEdge.class); - assertEquals(0, graph.edgeSet().size()); - - String vertexA = "A"; - String vertexB = "B"; - String vertexC = new StringBuffer("A").toString(); - - 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); - } -} - -// End CycleDetectorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/DijkstraShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/DijkstraShortestPathTest.java deleted file mode 100644 index 158f77953b1..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/DijkstraShortestPathTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * DijkstraShortestPathTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 03-Sept-2003 : Initial revision (JVS); - * 14-Jan-2006 : Factored out ShortestPathTestCase (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public class DijkstraShortestPathTest - extends ShortestPathTestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testConstructor() - { - DijkstraShortestPath path; - Graph g = create(); - - path = - new DijkstraShortestPath( - g, - V3, - V4, - Double.POSITIVE_INFINITY); - assertEquals( - Arrays.asList( - new DefaultEdge[] { - e13, - e12, - e24 - }), - path.getPathEdgeList()); - assertEquals(10.0, path.getPathLength(), 0); - - path = - new DijkstraShortestPath( - g, - V3, - V4, - 7); - assertNull(path.getPathEdgeList()); - assertEquals(Double.POSITIVE_INFINITY, path.getPathLength(), 0); - } - - protected List findPathBetween( - Graph g, - String src, - String dest) - { - return DijkstraShortestPath.findPathBetween(g, src, dest); - } -} - -// End DijkstraShortestPathTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsBlossomShrinkingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsBlossomShrinkingTest.java deleted file mode 100644 index b1c90171273..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsBlossomShrinkingTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2012, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * EdmondsBlossomShrinkingTest.java - * ------------------------- - * (C) Copyright 2012-2012, by Alejandro Ramon Lopez del Huerto and Contributors. - * - * Original Author: Alejandro Ramon Lopez del Huerto - * Contributor(s): - * - * Changes - * ------- - * 24-Jan-2012 : Initial revision (ARLH); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - -/** - * . - * - * @author Alejandro R. Lopez del Huerto - * @since Jan 24, 2012 - */ -public final class EdmondsBlossomShrinkingTest extends TestCase -{ - public void testOne() - { - // create an undirected graph - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); - - Integer v1 = 1; - Integer v2 = 2; - Integer v3 = 3; - Integer v4 = 4; - - g.addVertex(v1); - g.addVertex(v2); - g.addVertex(v3); - g.addVertex(v4); - - DefaultEdge e12 = g.addEdge(v1, v2); - DefaultEdge e23 = g.addEdge(v2, v3); - DefaultEdge e24 = g.addEdge(v2, v4); - DefaultEdge e34 = g.addEdge(v3, v4); - - // compute max match - EdmondsBlossomShrinking matcher = - new EdmondsBlossomShrinking(); - Set match = matcher.findMatch(g); - assertEquals(2, match.size()); - assertTrue(match.contains(e12)); - assertTrue(match.contains(e34)); - } -} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsKarpMaximumFlowTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsKarpMaximumFlowTest.java deleted file mode 100644 index b64c01949a8..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/EdmondsKarpMaximumFlowTest.java +++ /dev/null @@ -1,254 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EdmondsKarpMaximumFlowTest.java - * ----------------- - * (C) Copyright 2008-2008, by Ilya Razenshteyn and Contributors. - * - * Original Author: Ilya Razenshteyn - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.graph.*; - - -public final class EdmondsKarpMaximumFlowTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testCornerCases() - { - DirectedWeightedMultigraph simple = - new DirectedWeightedMultigraph( - DefaultWeightedEdge.class); - simple.addVertex(0); - simple.addVertex(1); - DefaultWeightedEdge e = simple.addEdge(0, 1); - try { - new EdmondsKarpMaximumFlow(null); - fail(); - } catch (NullPointerException ex) { - } - try { - new EdmondsKarpMaximumFlow( - simple, - -0.1); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - simple.setEdgeWeight(e, -1.0); - new EdmondsKarpMaximumFlow(simple); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - simple.setEdgeWeight(e, 1.0); - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(0, 1); - Map flow = solver.getMaximumFlow(); - flow.put(e, 25.0); - fail(); - } catch (UnsupportedOperationException ex) { - } - try { - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(2, 0); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(1, 2); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(0, 0); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(null, 0); - fail(); - } catch (IllegalArgumentException ex) { - } - try { - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow( - simple); - solver.calculateMaximumFlow(0, null); - fail(); - } catch (IllegalArgumentException ex) { - } - } - - /** - * . - */ - public void testLogic() - { - runTest( - new int[] {}, - new int[] {}, - new double[] {}, - new int[] { 1 }, - new int[] { 4057218 }, - new double[] { 0.0 }); - runTest( - 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 }, - new double[] { 2 }); - runTest( - 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 }, - new double[] { 4.0 }); - runTest( - 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 }, - new double[] { 2000000000.0 }); - } - - private void runTest( - int [] tails, - int [] heads, - double [] capacities, - int [] sources, - int [] sinks, - double [] expectedResults) - { - 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++) { - network.addVertex(tails[i]); - network.addVertex(heads[i]); - DefaultWeightedEdge e = network.addEdge(tails[i], heads[i]); - network.setEdgeWeight(e, capacities[i]); - } - assertTrue(sources.length == sinks.length); - int q = sources.length; - for (int i = 0; i < q; i++) { - network.addVertex(sources[i]); - network.addVertex(sinks[i]); - } - EdmondsKarpMaximumFlow solver = - new EdmondsKarpMaximumFlow(network); - assertTrue(solver.getCurrentSource() == null); - assertTrue(solver.getCurrentSink() == null); - assertTrue(solver.getMaximumFlowValue() == null); - assertTrue(solver.getMaximumFlow() == null); - for (int i = 0; i < q; i++) { - solver.calculateMaximumFlow(sources[i], sinks[i]); - assertTrue(solver.getCurrentSource().equals(sources[i])); - assertTrue(solver.getCurrentSink().equals(sinks[i])); - double flowValue = solver.getMaximumFlowValue(); - Map flow = solver.getMaximumFlow(); - assertEquals( - expectedResults[i], - flowValue, - EdmondsKarpMaximumFlow.DEFAULT_EPSILON); - for (DefaultWeightedEdge e : network.edgeSet()) { - assertTrue(flow.containsKey(e)); - } - for (DefaultWeightedEdge e : flow.keySet()) { - assertTrue(network.containsEdge(e)); - assertTrue( - flow.get(e) >= -EdmondsKarpMaximumFlow.DEFAULT_EPSILON); - assertTrue( - flow.get(e) - <= (network.getEdgeWeight(e) - + EdmondsKarpMaximumFlow.DEFAULT_EPSILON)); - } - 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(sources[i])) { - assertEquals( - -flowValue, - balance, - EdmondsKarpMaximumFlow.DEFAULT_EPSILON); - } else if (v.equals(sinks[i])) { - assertEquals( - flowValue, - balance, - EdmondsKarpMaximumFlow.DEFAULT_EPSILON); - } else { - assertEquals( - 0.0, - balance, - EdmondsKarpMaximumFlow.DEFAULT_EPSILON); - } - } - } - } -} - -// End EdmondsKarpMaximumFlowTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/EulerianCircuitTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/EulerianCircuitTest.java deleted file mode 100644 index 04aaef7aa56..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/EulerianCircuitTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * EulerianCircuitTest.java - * ------------------- - * (C) Copyright 2008-2008, by Andrew Newell and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Andrew Newell - */ -public class EulerianCircuitTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testEulerianCircuit() - { - UndirectedGraph completeGraph1 = - new SimpleGraph( - DefaultEdge.class); - CompleteGraphGenerator completeGenerator1 = - new CompleteGraphGenerator( - 6); - completeGenerator1.generateGraph( - completeGraph1, - new ClassBasedVertexFactory(Object.class), - null); - - // A complete graph of order 6 will have all vertices with degree 5 - // which is odd, therefore this graph is not Eulerian - assertFalse(EulerianCircuit.isEulerian(completeGraph1)); - assertTrue( - EulerianCircuit.getEulerianCircuitVertices(completeGraph1) == null); - - UndirectedGraph completeGraph2 = - new SimpleGraph( - DefaultEdge.class); - CompleteGraphGenerator completeGenerator2 = - new CompleteGraphGenerator( - 5); - completeGenerator2.generateGraph( - completeGraph2, - new ClassBasedVertexFactory(Object.class), - null); - assertTrue(EulerianCircuit.isEulerian(completeGraph2)); - - // There are 10 edges total in this graph, so an Eulerian circuit - // labeled by vertices should have 11 vertices - assertEquals( - 11, - EulerianCircuit.getEulerianCircuitVertices(completeGraph2).size()); - } -} - -// End EulerianCircuitTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/FloydWarshallShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/FloydWarshallShortestPathsTest.java deleted file mode 100644 index e5cb4b2ec3f..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/FloydWarshallShortestPathsTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2009, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * FloydWarshallShortestPathsTest.java - * ------------------------- - * (C) Copyright 2009-2009, by Tom Larkworthy and Contributors - * - * Original Author: Tom Larkworthy - * Contributors: Andrea Pagani - * - * $Id$ - * - * Changes - * ------- - * 29-Jun-2009 : Initial revision (TL); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * @author Tom Larkworthy - * @version $Id$ - */ -public class FloydWarshallShortestPathsTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testCompareWithDijkstra() - { - RandomGraphGenerator gen = - new RandomGraphGenerator( - 10, - 15); - VertexFactory f = - new VertexFactory() { - int gid; - - public Integer createVertex() - { - return gid++; - } - }; - - for (int i = 0; i < 10; i++) { - SimpleDirectedGraph directed = - new SimpleDirectedGraph( - DefaultWeightedEdge.class); - - gen.generateGraph(directed, f, new HashMap()); - - // setup our shortest path measurer - FloydWarshallShortestPaths fw = - new FloydWarshallShortestPaths( - directed); - - for (Integer v1 : directed.vertexSet()) { - for (Integer v2 : directed.vertexSet()) { - double fwSp = fw.shortestDistance(v1, v2); - double dijSp = - new DijkstraShortestPath( - directed, - v1, - v2).getPathLength(); - assertTrue( - (Math.abs(dijSp - fwSp) < .01) - || (Double.isInfinite(fwSp) - && Double.isInfinite(dijSp))); - } - } - - SimpleGraph undirected = - new SimpleGraph( - DefaultWeightedEdge.class); - - gen.generateGraph(undirected, f, new HashMap()); - - // setup our shortest path measurer - fw = new FloydWarshallShortestPaths( - undirected); - - for (Integer v1 : undirected.vertexSet()) { - for (Integer v2 : undirected.vertexSet()) { - double fwSp = fw.shortestDistance(v1, v2); - double dijSp = - new DijkstraShortestPath( - undirected, - v1, - v2).getPathLength(); - assertTrue( - (Math.abs(dijSp - fwSp) < .01) - || (Double.isInfinite(fwSp) - && Double.isInfinite(dijSp))); - } - } - } - } - - private static UndirectedGraph createStringGraph() - { - UndirectedGraph 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, v1); - g.addEdge(v3, v4); - - return g; - } - - public void testDiameter() - { - UndirectedGraph stringGraph = createStringGraph(); - FloydWarshallShortestPaths testFWPath = - new FloydWarshallShortestPaths(stringGraph); - double diameter = testFWPath.getDiameter(); - assertEquals(2.0, diameter); - } -} - -// End FloydWarshallShortestPathsTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/HamiltonianCycleTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/HamiltonianCycleTest.java deleted file mode 100644 index 0d6cc80db63..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/HamiltonianCycleTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * HamiltonianCycleTest.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Andrew Newell - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 17-Feb-2008 : Initial revision (AN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Andrew Newell - */ -public class HamiltonianCycleTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - // ~ Methods - // ---------------------------------------------------------------- - - /** - * . - */ - public void testHamiltonianCycle() - { - SimpleWeightedGraph completeGraph = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); - CompleteGraphGenerator completeGraphGenerator = - new CompleteGraphGenerator( - 6); - completeGraphGenerator.generateGraph( - completeGraph, - new ClassBasedVertexFactory(Object.class), - null); - - assertTrue( - HamiltonianCycle.getApproximateOptimalForCompleteGraph( - completeGraph).size() == 6); - - List vertices = - new LinkedList(completeGraph.vertexSet()); - completeGraph.removeEdge( - completeGraph.getEdge(vertices.get(0), - vertices.get(1))); - - assertTrue( - HamiltonianCycle.getApproximateOptimalForCompleteGraph( - completeGraph) == null); - } -} - -// End HamiltonianCycleTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPDiscardsValidPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KSPDiscardsValidPathsTest.java deleted file mode 100644 index 788b07cc90a..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPDiscardsValidPathsTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KSPDiscardsValidPathsTest.java - * ------------------------- - * (C) Copyright 2010-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id: MaskFunctor.java 645 2008-09-30 19:44:48Z perfecthash $ - * - * Changes - * ------- - * 06-Dec-2010 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import junit.framework.*; - -import org.jgrapht.graph.*; - - -@SuppressWarnings("unchecked") -public class KSPDiscardsValidPathsTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Example with a biconnected graph but not 3-connected. With a graph not - * 3-connected, the start vertex and the end vertex can be disconnected by 2 - * paths. - */ - public void testNot3connectedGraph() - { - WeightedMultigraph graph; - KShortestPaths paths; - - graph = - new WeightedMultigraph( - DefaultWeightedEdge.class); - graph.addVertex("S"); - graph.addVertex("T"); - graph.addVertex("A"); - graph.addVertex("B"); - graph.addVertex("C"); - graph.addVertex("D"); - graph.addVertex("E"); - graph.addVertex("F"); - graph.addVertex("G"); - graph.addVertex("H"); - graph.addVertex("I"); - graph.addVertex("J"); - graph.addVertex("K"); - graph.addVertex("L"); - - this.addGraphEdge(graph, "S", "A", 1.0); - this.addGraphEdge(graph, "A", "T", 1.0); - this.addGraphEdge(graph, "A", "B", 1.0); - this.addGraphEdge(graph, "B", "T", 1.0); - this.addGraphEdge(graph, "B", "C", 1.0); - - this.addGraphEdge(graph, "C", "D", 1.0); - this.addGraphEdge(graph, "C", "E", 1.0); - this.addGraphEdge(graph, "C", "F", 1.0); - this.addGraphEdge(graph, "D", "G", 1.0); - this.addGraphEdge(graph, "E", "G", 1.0); - this.addGraphEdge(graph, "F", "G", 1.0); - - this.addGraphEdge(graph, "G", "H", 1.0); - this.addGraphEdge(graph, "H", "I", 1.0); - this.addGraphEdge(graph, "I", "J", 1.0); - this.addGraphEdge(graph, "J", "K", 1.0); - this.addGraphEdge(graph, "K", "L", 1.0); - this.addGraphEdge(graph, "L", "S", 1.0); - - paths = new KShortestPaths(graph, "S", 3); - - Assert.assertTrue(paths.getPaths("T").size() == 3); - } - - /** - * JUnit test for the bug reported by Bruno Maoili. Example with a connected - * graph but not 2-connected. With a graph not 2-connected, the start vertex - * and the end vertex can be disconnected by 1 path. - */ - public void testBrunoMaoili() - { - WeightedMultigraph graph; - KShortestPaths paths; - - graph = - new WeightedMultigraph( - DefaultWeightedEdge.class); - graph.addVertex("A"); - graph.addVertex("B"); - graph.addVertex("C"); - graph.addVertex("D"); - graph.addVertex("E"); - - this.addGraphEdge(graph, "A", "B", 1.0); - this.addGraphEdge(graph, "A", "C", 2.0); - this.addGraphEdge(graph, "B", "D", 1.0); - this.addGraphEdge(graph, "B", "D", 1.0); - this.addGraphEdge(graph, "B", "D", 1.0); - this.addGraphEdge(graph, "B", "E", 1.0); - this.addGraphEdge(graph, "C", "D", 1.0); - - paths = new KShortestPaths(graph, "A", 2); - Assert.assertTrue(paths.getPaths("E").size() == 2); - - paths = new KShortestPaths(graph, "A", 3); - Assert.assertTrue(paths.getPaths("E").size() == 3); - - paths = new KShortestPaths(graph, "A", 4); - Assert.assertTrue(paths.getPaths("E").size() == 4); - } - - private void addGraphEdge( - WeightedMultigraph graph, - String sourceVertex, - String targetVertex, - double weight) - { - DefaultWeightedEdge edge = new DefaultWeightedEdge(); - - graph.addEdge(sourceVertex, targetVertex, edge); - graph.setEdgeWeight(edge, weight); - } -} - -// End KSPDiscardsValidPathsTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleGraph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleGraph.java deleted file mode 100644 index d0f992a59e1..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleGraph.java +++ /dev/null @@ -1,123 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KSPExampleGraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 23-Sep-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * - */ -@SuppressWarnings("unchecked") -public class KSPExampleGraph - extends SimpleWeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -1850978181764235655L; - - //~ Instance fields -------------------------------------------------------- - - public Object edgeAD; - - public Object edgeBT; - - public Object edgeCB; - - public Object edgeCT; - - public Object edgeDE; - - public Object edgeEC; - - public Object edgeSA; - - public Object edgeST; - - //~ Constructors ----------------------------------------------------------- - - /** - * - */ - public KSPExampleGraph() - { - super(DefaultWeightedEdge.class); - - addVertices(); - addEdges(); - } - - //~ Methods ---------------------------------------------------------------- - - private void addEdges() - { - this.edgeST = this.addEdge("S", "T"); - this.edgeSA = this.addEdge("S", "A"); - this.edgeAD = this.addEdge("A", "D"); - this.edgeDE = this.addEdge("D", "E"); - this.edgeEC = this.addEdge("E", "C"); - this.edgeCB = this.addEdge("C", "B"); - this.edgeCT = this.addEdge("C", "T"); - this.edgeBT = this.addEdge("B", "T"); - - setEdgeWeight(this.edgeST, 1); - setEdgeWeight(this.edgeSA, 100); - setEdgeWeight(this.edgeAD, 1); - setEdgeWeight(this.edgeDE, 1); - setEdgeWeight(this.edgeEC, 1); - setEdgeWeight(this.edgeCB, 1); - setEdgeWeight(this.edgeCT, 1); - setEdgeWeight(this.edgeBT, 1); - } - - private void addVertices() - { - addVertex("S"); - addVertex("T"); - addVertex("A"); - addVertex("B"); - addVertex("C"); - addVertex("D"); - addVertex("E"); - } -} - -// End KSPExampleGraph.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleTest.java deleted file mode 100644 index 7c183c106f7..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExampleTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KSPExampleTest.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 23-Sep-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import junit.framework.*; - -import org.jgrapht.graph.*; - - -@SuppressWarnings("unchecked") -public class KSPExampleTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testFourReturnedPathsJGraphT() - { - SimpleWeightedGraph graph = new KSPExampleGraph(); - - Object sourceVertex = "S"; - KShortestPaths ksp = new KShortestPaths(graph, sourceVertex, 4); - - Object targetVertex = "T"; - assertEquals(3, ksp.getPaths(targetVertex).size()); - } - - public void testThreeReturnedPathsJGraphT() - { - SimpleWeightedGraph graph = new KSPExampleGraph(); - - Object sourceVertex = "S"; - int nbPaths = 3; - KShortestPaths ksp = new KShortestPaths(graph, sourceVertex, nbPaths); - - Object targetVertex = "T"; - assertEquals(nbPaths, ksp.getPaths(targetVertex).size()); - } - - public void testTwoReturnedPathsJGraphT() - { - SimpleWeightedGraph graph = new KSPExampleGraph(); - - Object sourceVertex = "S"; - int nbPaths = 2; - KShortestPaths ksp = new KShortestPaths(graph, sourceVertex, nbPaths); - - Object targetVertex = "T"; - assertEquals(nbPaths, ksp.getPaths(targetVertex).size()); - } -} - -// End $file.name$ diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph4.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph4.java deleted file mode 100644 index ab6a2782e8d..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph4.java +++ /dev/null @@ -1,109 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathCompleteGraph4.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class KShortestPathCompleteGraph4 - extends SimpleWeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -4091707260999013100L; - - //~ Instance fields -------------------------------------------------------- - - public Object e12; - - public Object e13; - - public Object e23; - - public Object eS1; - - public Object eS2; - - public Object eS3; - - //~ Constructors ----------------------------------------------------------- - - public KShortestPathCompleteGraph4() - { - super(DefaultWeightedEdge.class); - addVertices(); - addEdges(); - } - - //~ Methods ---------------------------------------------------------------- - - private void addEdges() - { - this.eS1 = addEdge("vS", "v1"); - this.eS2 = addEdge("vS", "v2"); - this.eS3 = addEdge("vS", "v3"); - this.e12 = addEdge("v1", "v2"); - this.e13 = addEdge("v1", "v3"); - this.e23 = addEdge("v2", "v3"); - - setEdgeWeight(this.eS1, 1.0); - setEdgeWeight(this.eS2, 1.0); - setEdgeWeight(this.eS3, 1000.0); - setEdgeWeight(this.e12, 1.0); - setEdgeWeight(this.e13, 1.0); - setEdgeWeight(this.e23, 1.0); - } - - private void addVertices() - { - addVertex("vS"); - addVertex("v1"); - addVertex("v2"); - addVertex("v3"); - } -} - -// End KShortestPathCompleteGraph4.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph5.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph5.java deleted file mode 100644 index 982945d4d1b..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph5.java +++ /dev/null @@ -1,127 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathCompleteGraph5.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class KShortestPathCompleteGraph5 - extends SimpleWeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -3289497257289559394L; - - //~ Instance fields -------------------------------------------------------- - - public Object e12; - - public Object e13; - - public Object e14; - - public Object e23; - - public Object e24; - - public Object e34; - - public Object eS1; - - public Object eS2; - - public Object eS3; - - public Object eS4; - - //~ Constructors ----------------------------------------------------------- - - public KShortestPathCompleteGraph5() - { - super(DefaultWeightedEdge.class); - - addVertices(); - addEdges(); - } - - //~ Methods ---------------------------------------------------------------- - - private void addEdges() - { - this.eS1 = addEdge("vS", "v1"); - this.eS2 = addEdge("vS", "v2"); - this.eS3 = addEdge("vS", "v3"); - this.eS4 = addEdge("vS", "v4"); - this.e12 = addEdge("v1", "v2"); - this.e13 = addEdge("v1", "v3"); - this.e14 = addEdge("v1", "v4"); - this.e23 = addEdge("v2", "v3"); - this.e24 = addEdge("v2", "v4"); - this.e34 = addEdge("v3", "v4"); - - setEdgeWeight(this.eS1, 1.0); - setEdgeWeight(this.eS2, 1.0); - setEdgeWeight(this.eS3, 1.0); - setEdgeWeight(this.eS4, 1000.0); - setEdgeWeight(this.e12, 1.0); - setEdgeWeight(this.e13, 1.0); - setEdgeWeight(this.e14, 1.0); - setEdgeWeight(this.e23, 1.0); - setEdgeWeight(this.e24, 1.0); - setEdgeWeight(this.e34, 1.0); - } - - private void addVertices() - { - addVertex("vS"); - addVertex("v1"); - addVertex("v2"); - addVertex("v3"); - addVertex("v4"); - } -} - -// End KShortestPathCompleteGraph5.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph6.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph6.java deleted file mode 100644 index cb1009fcfa8..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCompleteGraph6.java +++ /dev/null @@ -1,156 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathCompleteGraph6.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class KShortestPathCompleteGraph6 - extends SimpleWeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = 6310990195071210970L; - - //~ Instance fields -------------------------------------------------------- - - public Object e12; - - public Object e13; - - public Object e14; - - public Object e23; - - public Object e24; - - public Object e34; - - public Object eS1; - - public Object eS2; - - public Object eS3; - - public Object eS4; - - private Object e15; - - private Object e25; - - private Object e35; - - private Object e45; - - private Object eS5; - - //~ Constructors ----------------------------------------------------------- - - public KShortestPathCompleteGraph6() - { - super(DefaultWeightedEdge.class); - - addVertices(); - addEdges(); - } - - //~ Methods ---------------------------------------------------------------- - - private void addEdges() - { - this.eS1 = this.addEdge("vS", "v1"); - this.eS2 = this.addEdge("vS", "v2"); - this.eS3 = this.addEdge("vS", "v3"); - this.eS4 = this.addEdge("vS", "v4"); - this.eS5 = this.addEdge("vS", "v5"); - - this.e12 = this.addEdge("v1", "v2"); - this.e13 = this.addEdge("v1", "v3"); - this.e14 = this.addEdge("v1", "v4"); - this.e15 = this.addEdge("v1", "v5"); - - this.e23 = this.addEdge("v2", "v3"); - this.e24 = this.addEdge("v2", "v4"); - this.e25 = this.addEdge("v2", "v5"); - - this.e34 = this.addEdge("v3", "v4"); - this.e35 = this.addEdge("v3", "v5"); - - this.e45 = this.addEdge("v4", "v5"); - - setEdgeWeight(this.eS1, 1.0); - setEdgeWeight(this.eS2, 1.0); - setEdgeWeight(this.eS3, 1.0); - setEdgeWeight(this.eS4, 1.0); - setEdgeWeight(this.eS5, 1000.0); - - setEdgeWeight(this.e12, 1.0); - setEdgeWeight(this.e13, 1.0); - setEdgeWeight(this.e14, 1.0); - setEdgeWeight(this.e15, 1.0); - - setEdgeWeight(this.e23, 1.0); - setEdgeWeight(this.e24, 1.0); - setEdgeWeight(this.e25, 1.0); - - setEdgeWeight(this.e34, 1.0); - setEdgeWeight(this.e35, 1.0); - - setEdgeWeight(this.e45, 1.0); - } - - private void addVertices() - { - addVertex("vS"); - addVertex("v1"); - addVertex("v2"); - addVertex("v3"); - addVertex("v4"); - addVertex("v5"); - } -} - -// End KShortestPathCompleteGraph6.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCostTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCostTest.java deleted file mode 100644 index 0fac2cd9754..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathCostTest.java +++ /dev/null @@ -1,301 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathCostTest.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class KShortestPathCostTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testKShortestPathCompleteGraph4() - { - int nbPaths = 5; - - KShortestPathCompleteGraph4 graph = new KShortestPathCompleteGraph4(); - - KShortestPaths pathFinder = new KShortestPaths(graph, "vS", nbPaths); - List pathElements = pathFinder.getPaths("v3"); - - assertEquals( - "[[(vS : v1), (v1 : v3)], [(vS : v2), (v2 : v3)]," - + " [(vS : v2), (v1 : v2), (v1 : v3)], " - + "[(vS : v1), (v1 : v2), (v2 : v3)], " + "[(vS : v3)]]", - pathElements.toString()); - - assertEquals(5, pathElements.size(), 0); - GraphPath pathElement = (GraphPath) pathElements.get(0); - assertEquals(2, pathElement.getWeight(), 0); - - assertEquals( - Arrays.asList(new Object[] { graph.eS1, graph.e13 }), - pathElement.getEdgeList()); - } - - public void testPicture1Graph() - { - Picture1Graph picture1Graph = new Picture1Graph(); - - int maxSize = 10; - - KShortestPaths pathFinder = - new KShortestPaths(picture1Graph, "vS", - maxSize); - - // assertEquals(2, pathFinder.getPaths("v5").size()); - - List pathElements = pathFinder.getPaths("v5"); - GraphPath pathElement = (GraphPath) pathElements.get(0); - assertEquals( - Arrays.asList( - new Object[] { - picture1Graph.eS1, - picture1Graph.e15 - }), - pathElement.getEdgeList()); - - List vertices = Graphs.getPathVertexList(pathElement); - assertEquals( - Arrays.asList(new Object[] { "vS", "v1", "v5" }), - vertices); - - pathElement = (GraphPath) pathElements.get(1); - assertEquals( - Arrays.asList( - new Object[] { - picture1Graph.eS2, - picture1Graph.e25 - }), - pathElement.getEdgeList()); - - vertices = Graphs.getPathVertexList(pathElement); - assertEquals( - Arrays.asList(new Object[] { "vS", "v2", "v5" }), - vertices); - - pathElements = pathFinder.getPaths("v7"); - pathElement = (GraphPath) pathElements.get(0); - double lastCost = pathElement.getWeight(); - for (int i = 0; i < pathElements.size(); i++) { - pathElement = (GraphPath) pathElements.get(i); - double cost = pathElement.getWeight(); - - assertTrue(lastCost <= cost); - lastCost = cost; - } - } - - public void testShortestPathsInIncreasingOrder() - { - BiconnectedGraph biconnectedGraph = new BiconnectedGraph(); - verifyShortestPathsInIncreasingOrderOfWeight(biconnectedGraph); - - KShortestPathCompleteGraph4 kSPCompleteGraph4 = - new KShortestPathCompleteGraph4(); - verifyShortestPathsInIncreasingOrderOfWeight(kSPCompleteGraph4); - - KShortestPathCompleteGraph5 kSPCompleteGraph5 = - new KShortestPathCompleteGraph5(); - verifyShortestPathsInIncreasingOrderOfWeight(kSPCompleteGraph5); - - KShortestPathCompleteGraph6 kSPCompleteGraph6 = - new KShortestPathCompleteGraph6(); - verifyShortestPathsInIncreasingOrderOfWeight(kSPCompleteGraph6); - - KSPExampleGraph kSPExampleGraph = new KSPExampleGraph(); - verifyShortestPathsInIncreasingOrderOfWeight(kSPExampleGraph); - - NotBiconnectedGraph notBiconnectedGraph = new NotBiconnectedGraph(); - verifyShortestPathsInIncreasingOrderOfWeight(notBiconnectedGraph); - - Picture1Graph picture1Graph = new Picture1Graph(); - verifyShortestPathsInIncreasingOrderOfWeight(picture1Graph); - } - - public void testShortestPathsWeightsWithMaxSizeIncreases() - { - BiconnectedGraph biconnectedGraph = new BiconnectedGraph(); - verifyShortestPathsWeightsWithMaxSizeIncreases(biconnectedGraph); - - KShortestPathCompleteGraph4 kSPCompleteGraph4 = - new KShortestPathCompleteGraph4(); - verifyShortestPathsWeightsWithMaxSizeIncreases(kSPCompleteGraph4); - - KShortestPathCompleteGraph5 kSPCompleteGraph5 = - new KShortestPathCompleteGraph5(); - verifyShortestPathsWeightsWithMaxSizeIncreases(kSPCompleteGraph5); - - KShortestPathCompleteGraph6 kSPCompleteGraph6 = - new KShortestPathCompleteGraph6(); - verifyShortestPathsWeightsWithMaxSizeIncreases(kSPCompleteGraph6); - - KSPExampleGraph kSPExampleGraph = new KSPExampleGraph(); - verifyShortestPathsWeightsWithMaxSizeIncreases(kSPExampleGraph); - - NotBiconnectedGraph notBiconnectedGraph = new NotBiconnectedGraph(); - verifyShortestPathsWeightsWithMaxSizeIncreases(notBiconnectedGraph); - - Picture1Graph picture1Graph = new Picture1Graph(); - verifyShortestPathsWeightsWithMaxSizeIncreases(picture1Graph); - } - - private void verifyShortestPathsInIncreasingOrderOfWeight(Graph graph) - { - int maxSize = 20; - - for ( - Iterator sourceIterator = graph.vertexSet().iterator(); - sourceIterator.hasNext();) - { - Object sourceVertex = sourceIterator.next(); - - for ( - Iterator targetIterator = graph.vertexSet().iterator(); - targetIterator.hasNext();) - { - Object targetVertex = targetIterator.next(); - - if (targetVertex != sourceVertex) { - KShortestPaths pathFinder = - new KShortestPaths(graph, - sourceVertex, maxSize); - - List pathElements = pathFinder.getPaths(targetVertex); - if (pathElements == null) { - // no path exists between the start vertex and the end - // vertex - continue; - } - GraphPath pathElement = (GraphPath) pathElements.get(0); - double lastWeight = pathElement.getWeight(); - for (int i = 0; i < pathElements.size(); i++) { - pathElement = (GraphPath) pathElements.get(i); - double weight = pathElement.getWeight(); - assertTrue(lastWeight <= weight); - lastWeight = weight; - } - assertTrue(pathElements.size() <= maxSize); - } - } - } - } - - private void verifyShortestPathsWeightsWithMaxSizeIncreases(Graph graph) - { - int maxSizeLimit = 10; - - for ( - Iterator sourceIterator = graph.vertexSet().iterator(); - sourceIterator.hasNext();) - { - Object sourceVertex = sourceIterator.next(); - - for ( - Iterator targetIterator = graph.vertexSet().iterator(); - targetIterator.hasNext();) - { - Object targetVertex = targetIterator.next(); - - if (targetVertex != sourceVertex) { - KShortestPaths pathFinder = - new KShortestPaths(graph, - sourceVertex, 1); - List prevPathElementsResults = - pathFinder.getPaths(targetVertex); - - if (prevPathElementsResults == null) { - // no path exists between the start vertex and the - // end vertex - continue; - } - - for (int maxSize = 2; maxSize < maxSizeLimit; maxSize++) { - pathFinder = - new KShortestPaths(graph, sourceVertex, - maxSize); - List pathElementsResults = - pathFinder.getPaths(targetVertex); - - verifyWeightsConsistency( - prevPathElementsResults, - pathElementsResults); - } - } - } - } - } - - /** - * Verify weights consistency between the results when the max-size argument - * increases. - * - * @param prevPathElementsResults results obtained with a max-size argument - * equal to k - * @param pathElementsResults results obtained with a max-size argument - * equal to k+1 - */ - private void verifyWeightsConsistency( - List prevPathElementsResults, - List pathElementsResults) - { - for (int i = 0; i < prevPathElementsResults.size(); i++) { - GraphPath pathElementResult = - (GraphPath) pathElementsResults.get(i); - GraphPath prevPathElementResult = - (GraphPath) prevPathElementsResults.get(i); - assertTrue( - pathElementResult.getWeight() - == prevPathElementResult.getWeight()); - } - } -} - -// End KShortestPathCostTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathKValuesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathKValuesTest.java deleted file mode 100644 index 3f4ed2f0708..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KShortestPathKValuesTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * KShortestPathKValuesTest.java - * ------------------------- - * (C) Copyright 2007-2010, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * 06-Dec-2010 : Bugfixes (GB); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class KShortestPathKValuesTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * @param k - * @param n - * - * @return A(n,k). - */ - public static long permutation(int n, int k) - { - if (k <= n) { - return MathUtil.factorial(n) / MathUtil.factorial(n - k); - } else { - return 0; - } - } - - public void testMaxSizeValueCompleteGraph6() - { - KShortestPathCompleteGraph6 graph = new KShortestPathCompleteGraph6(); - - for ( - int maxSize = 1; - maxSize <= calculateNbElementaryPathsForCompleteGraph(6); - maxSize++) - { - KShortestPaths finder = new KShortestPaths(graph, "vS", maxSize); - - assertEquals(finder.getPaths("v1").size(), maxSize); - assertEquals(finder.getPaths("v2").size(), maxSize); - assertEquals(finder.getPaths("v3").size(), maxSize); - assertEquals(finder.getPaths("v4").size(), maxSize); - assertEquals(finder.getPaths("v5").size(), maxSize); - } - } - - public void testNbReturnedPaths() - { - KShortestPathCompleteGraph4 kSPCompleteGraph4 = - new KShortestPathCompleteGraph4(); - verifyNbPathsForAllPairsOfVertices(kSPCompleteGraph4); - - KShortestPathCompleteGraph5 kSPCompleteGraph5 = - new KShortestPathCompleteGraph5(); - verifyNbPathsForAllPairsOfVertices(kSPCompleteGraph5); - - KShortestPathCompleteGraph6 kSPCompleteGraph6 = - new KShortestPathCompleteGraph6(); - verifyNbPathsForAllPairsOfVertices(kSPCompleteGraph6); - } - - /** - * Compute the total number of paths between every pair of vertices in a - * complete graph with n vertices. - * - * @param n - * - * @return - */ - private long calculateNbElementaryPathsForCompleteGraph(int n) - { - long nbPaths = 0; - for (int k = 1; k <= (n - 1); k++) { - nbPaths = nbPaths + permutation(n - 2, k - 1); - } - return nbPaths; - } - - private void verifyNbPathsForAllPairsOfVertices(Graph graph) - { - long nbPaths = - calculateNbElementaryPathsForCompleteGraph( - graph.vertexSet().size()); - int maxSize = Integer.MAX_VALUE; - - for ( - Iterator sourceIterator = graph.vertexSet().iterator(); - sourceIterator.hasNext();) - { - Object sourceVertex = sourceIterator.next(); - - KShortestPaths finder = - new KShortestPaths(graph, sourceVertex, - maxSize); - for ( - Iterator targetIterator = graph.vertexSet().iterator(); - targetIterator.hasNext();) - { - Object targetVertex = targetIterator.next(); - if (targetVertex != sourceVertex) { - assertEquals(finder.getPaths(targetVertex).size(), nbPaths); - } - } - } - } -} - -// End KShortestPathKValuesTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KruskalMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/KruskalMinimumSpanningTreeTest.java deleted file mode 100644 index df7fe22447a..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/KruskalMinimumSpanningTreeTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * KruskalMinimumSpanningTreeTest.java - * ------------------------------ - * (C) Copyright 2010-2010, by Tom Conerly and Contributors. - * - * Original Author: Tom Conerly - * Contributor(s): - - * - * Changes - * ------- - * 02-Feb-2010 : Initial revision (TC); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -public class KruskalMinimumSpanningTreeTest - extends TestCase -{ - //~ 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 ---------------------------------------------------------------- - - /** - * . - */ - public void testMinimumSpanningTree() - { - Graph graph = createWeighted(); - - KruskalMinimumSpanningTree mst = - new KruskalMinimumSpanningTree(graph); - - assertEquals(15.0, mst.getSpanningTreeCost()); - - Set edges = mst.getEdgeSet(); - for (DefaultWeightedEdge edge : edges) { - assertTrue( - edge.equals(e12) || edge.equals(e13) || edge.equals(e24) - || edge.equals(e45)); - } - } - - protected Graph createWeighted() - { - Graph g; - double bias = 1; - - 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; - } -} - -// End KruskalMinimumSpanningTreeTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/NeighborIndexTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/NeighborIndexTest.java deleted file mode 100644 index a38b99935bf..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/NeighborIndexTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * NeighborIndexTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 12-Dec-2005 : Initial revision (CF); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Charles Fry - */ -public class NeighborIndexTest - extends TestCase -{ - //~ Static fields/initializers --------------------------------------------- - - private static final String V1 = "v1"; - private static final String V2 = "v2"; - private static final String V3 = "v3"; - - //~ Methods ---------------------------------------------------------------- - - 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 - ListenableUndirectedGraph g = - new ListenableUndirectedGraph( - Object.class); - g.addVertex(V1); - g.addVertex(V2); - - g.addEdge(V1, V2); - - NeighborIndex index = - new NeighborIndex(g); - g.addGraphListener(index); - - Set neighbors1 = index.neighborsOf(V1); - - assertEquals(1, neighbors1.size()); - assertEquals(true, neighbors1.contains(V2)); - - g.addVertex(V3); - g.addEdge(V3, V1); - - Set neighbors3 = index.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()); - } - - public void testDirectedNeighborSet() - { - ListenableDirectedGraph g = - new ListenableDirectedGraph( - Object.class); - g.addVertex(V1); - g.addVertex(V2); - - g.addEdge(V1, V2); - - DirectedNeighborIndex index = - new DirectedNeighborIndex(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()); - } -} - -// End NeighborIndexTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/NotBiconnectedGraph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/NotBiconnectedGraph.java deleted file mode 100644 index 39da7acdfd5..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/NotBiconnectedGraph.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * NotBiconnectedGraph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -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"); - } -} - -// End NotBiconnectedGraph.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1.jpg b/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1.jpg deleted file mode 100644 index 69b9e6acaaf..00000000000 Binary files a/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1.jpg and /dev/null differ diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1Graph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1Graph.java deleted file mode 100644 index 59aa9235abb..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/Picture1Graph.java +++ /dev/null @@ -1,147 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------- - * Picture1Graph.java - * ------------------------- - * (C) Copyright 2007-2008, by France Telecom - * - * Original Author: Guillaume Boulmier and Contributors. - * - * $Id$ - * - * Changes - * ------- - * 05-Jun-2007 : Initial revision (GB); - * - */ -package org.jgrapht.alg; - -import org.jgrapht.graph.*; - - -/** - * - * - * @author Guillaume Boulmier - * @since July 5, 2007 - */ -@SuppressWarnings("unchecked") -public class Picture1Graph - extends SimpleDirectedWeightedGraph -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = 5587737522611531029L; - - //~ Instance fields -------------------------------------------------------- - - public Object e15; - - public Object e25; - - public Object e27; - - public Object e37; - - public Object e47; - - public Object e56; - - public Object e57; - - public Object e67; - - public Object eS1; - - public Object eS2; - - public Object eS3; - - public Object eS4; - - public Object eS7; - - //~ Constructors ----------------------------------------------------------- - - /** - * - */ - public Picture1Graph() - { - super(DefaultWeightedEdge.class); - - addVertices(); - addEdges(); - } - - //~ Methods ---------------------------------------------------------------- - - private void addEdges() - { - this.eS1 = this.addEdge("vS", "v1"); - this.eS2 = this.addEdge("vS", "v2"); - this.eS3 = this.addEdge("vS", "v3"); - this.eS4 = this.addEdge("vS", "v4"); - this.eS7 = this.addEdge("vS", "v7"); - this.e15 = this.addEdge("v1", "v5"); - this.e25 = this.addEdge("v2", "v5"); - this.e27 = this.addEdge("v2", "v7"); - this.e37 = this.addEdge("v3", "v7"); - this.e47 = this.addEdge("v4", "v7"); - this.e56 = this.addEdge("v5", "v6"); - this.e57 = this.addEdge("v5", "v7"); - this.e67 = this.addEdge("v6", "v7"); - - setEdgeWeight(this.eS1, 3.0); - setEdgeWeight(this.eS2, 2.0); - setEdgeWeight(this.eS3, 10.0); - setEdgeWeight(this.eS4, 15.0); - setEdgeWeight(this.eS7, 15.0); - setEdgeWeight(this.e15, 3.0); - setEdgeWeight(this.e25, 6.0); - setEdgeWeight(this.e27, 10.0); - setEdgeWeight(this.e37, 20.0); - setEdgeWeight(this.e47, 5.0); - setEdgeWeight(this.e56, -3.0); - setEdgeWeight(this.e57, 4.0); - setEdgeWeight(this.e67, 5.0); - } - - private void addVertices() - { - addVertex("vS"); - addVertex("v1"); - addVertex("v2"); - addVertex("v3"); - addVertex("v4"); - addVertex("v5"); - addVertex("v6"); - addVertex("v7"); - } -} - -// End Picture1Graph.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/ShortestPathTestCase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/ShortestPathTestCase.java deleted file mode 100644 index cdef6283cf8..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/ShortestPathTestCase.java +++ /dev/null @@ -1,161 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * ShortestPathTestCase.java - * ------------------------------ - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 14-Jan-2006 : Factored out of DijkstraShortestPathTest (JVS); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public abstract class ShortestPathTestCase - extends TestCase -{ - //~ 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 ---------------------------------------------------------------- - - /** - * . - */ - public void testPathBetween() - { - List path; - Graph g = create(); - - path = findPathBetween(g, V1, V2); - assertEquals(Arrays.asList(new DefaultEdge[] { e12 }), path); - - path = findPathBetween(g, V1, V4); - assertEquals(Arrays.asList( - new DefaultEdge[] { - e12, - e24 - }), path); - - path = findPathBetween(g, V1, V5); - assertEquals(Arrays.asList( - new DefaultEdge[] { - e12, - e24, - e45 - }), path); - - path = findPathBetween(g, V3, V4); - assertEquals(Arrays.asList( - new DefaultEdge[] { - 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; - } -} - -// End ShortestPathTestCase.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/StoerWagnerMinimumCutTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/StoerWagnerMinimumCutTest.java index 63bfa8c59c4..ef9983839cc 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/StoerWagnerMinimumCutTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/StoerWagnerMinimumCutTest.java @@ -1,79 +1,55 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2011, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2011-2023, by Robby McKilliam and Contributors. * - * 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. - */ -/* ---------------- - * StoerWagnerMinimumCutTest.java - * ---------------- - * (C) Copyright 2011-2011, by Robby McKilliam and Contributors. - * - * Original Author: Robby McKilliam - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id: StoerWagnerMinimumCut.java $ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 java.util.*; - -import junit.framework.*; - import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Robby McKilliam */ public class StoerWagnerMinimumCutTest - extends TestCase { - //~ Instance fields -------------------------------------------------------- + // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; - - //~ Constructors ----------------------------------------------------------- - - public StoerWagnerMinimumCutTest() - { - } - - //~ Methods ---------------------------------------------------------------- + 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); + new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); @@ -91,20 +67,19 @@ public void testMinCut14() e = g.addEdge(v3, v4); g.setEdgeWeight(e, 1.0); - StoerWagnerMinimumCut mincut = - new StoerWagnerMinimumCut(g); + StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); - assertEquals(4.0, mincut.bestcutweight, 0.000001); + assertEquals(4.0, mincut.minCutWeight(), 0.000001); } /** * Test of mergeVertices method, of class StoerWagnerMinimumCut. */ + @Test public void testMinCutDisconnected() { SimpleWeightedGraph g = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); + new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); @@ -118,11 +93,225 @@ public void testMinCutDisconnected() e = g.addEdge(v2, v3); g.setEdgeWeight(e, 1.0); - StoerWagnerMinimumCut mincut = - new StoerWagnerMinimumCut(g); + StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); - assertEquals(0.0, mincut.bestcutweight, 0.000001); + 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)); } -} -// End StoerWagnerMinimumCutTest.java + /** + * 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); + // No vertices + assertThrows(IllegalArgumentException.class, () -> new StoerWagnerMinimumCut<>(g)); + } + + /** + * Test of StoerWagnerMinimumCut on empty and small graphs + */ + @Test + public void testMinCutSingleton() + { + SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); + // 1 vertex + g.addVertex(v1); + assertThrows(IllegalArgumentException.class, () -> new StoerWagnerMinimumCut<>(g)); + } + + /** + * 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)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveClosureTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveClosureTest.java index 54e2fd81f58..0840853b80c 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveClosureTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveClosureTest.java @@ -1,120 +1,130 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2007-2023, by Vinayak R Borkar and Contributors. * - * 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. - */ -/* ------------------------------ - * TransitiveClosureTest.java - * ------------------------------ - * (C) Copyright 2007, by Vinayak R. Borkar. + * JGraphT : a free Java graph-theory library * - * Original Author: Vinayak R. Borkar - * Contributor(s): + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 5-May-2007: Initial revision (VRB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 junit.framework.*; - -import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** */ public class TransitiveClosureTest - extends TestCase { - //~ Methods ---------------------------------------------------------------- + // ~ Methods ---------------------------------------------------------------- + @Test public void testLinearGraph() { - SimpleDirectedGraph graph = - new SimpleDirectedGraph(DefaultEdge.class); - - int N = 10; - LinearGraphGenerator gen = - new LinearGraphGenerator(N); - - VertexFactory vf = - new VertexFactory() { - private int m_index = 0; - - public Integer createVertex() - { - return Integer.valueOf(m_index++); - } - }; - gen.generateGraph(graph, vf, null); + 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(Integer.valueOf(i), Integer.valueOf(j)) - != null); + 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)); } } } + @Test public void testRingGraph() { - SimpleDirectedGraph graph = - new SimpleDirectedGraph(DefaultEdge.class); - - int N = 10; - RingGraphGenerator gen = - new RingGraphGenerator(N); - - VertexFactory vf = - new VertexFactory() { - private int m_index = 0; - - public Integer createVertex() - { - return Integer.valueOf(m_index++); - } - }; - gen.generateGraph(graph, vf, null); + 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(Integer.valueOf(i), Integer.valueOf(j)) - != null)); + assertEquals(n * (n - 1), graph.edgeSet().size()); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + assertTrue((i == j) || (graph.getEdge(i, j) != null)); } } } -} -// End TransitiveClosureTest.java + @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)); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveReductionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveReductionTest.java new file mode 100644 index 00000000000..fce930deb0a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveReductionTest.java @@ -0,0 +1,315 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +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[][] expectedPathMatrix = 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 pathMatrix + int[][] pathMatrix = new int[n][n]; + { + { + System.arraycopy(MATRIX, 0, pathMatrix, 0, MATRIX.length); + + final BitSet[] pathMatrixAsBitSetArray = asBitSetArray(pathMatrix); + + TransitiveReduction.transformToPathMatrix(pathMatrixAsBitSetArray); + + pathMatrix = asIntArray(pathMatrixAsBitSetArray); + } + // System.out.println(Arrays.deepToString(path_matrix) + " path + // matrix"); + + assertArrayEquals(expectedPathMatrix, pathMatrix); + } + + // calc transitive reduction + { + int[][] transitivelyReducedMatrix = new int[n][n]; + { + System.arraycopy(pathMatrix, 0, transitivelyReducedMatrix, 0, pathMatrix.length); + + final BitSet[] transitivelyReducedMatrixAsBitSetArray = + asBitSetArray(transitivelyReducedMatrix); + + TransitiveReduction.transitiveReduction(transitivelyReducedMatrixAsBitSetArray); + + transitivelyReducedMatrix = asIntArray(transitivelyReducedMatrixAsBitSetArray); + } + + // System.out.println(Arrays.deepToString(transitively_reduced_matrix) + // + " transitive reduction"); + + assertArrayEquals(EXPECTED_TRANSITIVELY_REDUCED_MATRIX, transitivelyReducedMatrix); + } + } + + 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 + public void testReduceNull() + { + assertThrows(NullPointerException.class, () -> TransitiveReduction.INSTANCE.reduce(null)); + } + + @Test + public void testReduceNoVertexNoEdge() + { + SimpleDirectedGraph graph = + new SimpleDirectedGraph<>(DefaultEdge.class); + TransitiveReduction.INSTANCE.reduce(graph); + assertEquals(0, graph.vertexSet().size()); + assertEquals(0, graph.edgeSet().size()); + } + + @Test + public void testReduceSomeVerticesNoEdge() + { + SimpleDirectedGraph graph = + new SimpleDirectedGraph<>(DefaultEdge.class); + graph.addVertex("x"); + graph.addVertex("y"); + graph.addVertex("z"); + TransitiveReduction.INSTANCE.reduce(graph); + assertEquals(3, graph.vertexSet().size()); + assertEquals(0, graph.edgeSet().size()); + } + + @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(3, graph.vertexSet().size()); + assertEquals(2, graph.edgeSet().size()); + + // reduce ! + TransitiveReduction.INSTANCE.reduce(graph); + + assertEquals(3, graph.vertexSet().size()); + assertEquals(2, graph.edgeSet().size()); + + 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(3, graph.vertexSet().size()); + assertEquals(3, graph.edgeSet().size()); + + // reduce ! + TransitiveReduction.INSTANCE.reduce(graph); + + assertEquals(3, graph.vertexSet().size()); + assertEquals(2, graph.edgeSet().size()); + + 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(6, graph.vertexSet().size()); + assertEquals(6, graph.edgeSet().size()); + + // reduce ! + TransitiveReduction.INSTANCE.reduce(graph); + + assertEquals(6, graph.vertexSet().size()); + assertEquals(5, graph.edgeSet().size()); + + 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(5, graph.vertexSet().size()); + assertEquals(6, graph.edgeSet().size()); + + // reduce ! + TransitiveReduction.INSTANCE.reduce(graph); + + assertEquals(5, graph.vertexSet().size()); + assertEquals(4, graph.edgeSet().size()); + + // 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[][] actualTransitivelyReducedMatrix = fromDirectedGraphToMatrix(graph); + assertArrayEquals(EXPECTED_TRANSITIVELY_REDUCED_MATRIX, actualTransitivelyReducedMatrix); + } + + 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 i1 = vertices.indexOf(v1); + final int i2 = vertices.indexOf(v2); + + matrix[i1][i2] = 1; + } + return matrix; + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/VertexCoversTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/VertexCoversTest.java deleted file mode 100644 index 7c5fd75dc89..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/VertexCoversTest.java +++ /dev/null @@ -1,151 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------- - * VertexCoversTest.java - * --------------------- - * (C) Copyright 2003-2008, by Linda Buisman and Contributors. - * - * Original Author: Linda Buisman - * Contributor(s): Barak Naveh - * - * $Id$ - * - * Changes - * ------- - * 06-Nov-2003 : Initial revision (LB); - * 10-Nov-2003 : Adapted to VertexCovers (BN); - * - */ -package org.jgrapht.alg; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * Tests the vertex cover algorithms. - * - * @author Linda Buisman - * @since Nov 6, 2003 - */ -public class VertexCoversTest - extends TestCase -{ - //~ Static fields/initializers --------------------------------------------- - - private static final int TEST_GRAPH_SIZE = 200; - private static final int TEST_REPEATS = 20; - - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testFind2ApproximationCover() - { - for (int i = 0; i < TEST_REPEATS; i++) { - Graph g = createRandomGraph(); - assertTrue( - isCover(VertexCovers.find2ApproximationCover(g), g)); - } - } - - /** - * . - */ - public void testFindGreedyCover() - { - for (int i = 0; i < TEST_REPEATS; i++) { - Graph g = createRandomGraph(); - Set c = - VertexCovers.findGreedyCover( - Graphs.undirectedGraph(g)); - assertTrue(isCover(c, g)); - } - } - - /** - * 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 vertexSet the vertices to be tested for covering the graph. - * @param g the graph to be covered. - * - * @return - */ - private boolean isCover( - Set vertexSet, - Graph g) - { - Set uncoveredEdges = new HashSet(g.edgeSet()); - - for (Iterator i = vertexSet.iterator(); i.hasNext();) { - uncoveredEdges.removeAll(g.edgesOf(i.next())); - } - - return uncoveredEdges.size() == 0; - } - - /** - * Create a random graph of TEST_GRAPH_SIZE nodes. - * - * @return - */ - private Graph createRandomGraph() - { - // TODO: move random graph generator to be under GraphGenerator - // framework. - Pseudograph g = - new Pseudograph(DefaultEdge.class); - - for (int i = 0; i < TEST_GRAPH_SIZE; i++) { - g.addVertex(new Integer(i)); - } - - List vertices = new ArrayList(g.vertexSet()); - - // join every vertex with a random number of other vertices - for (int source = 0; source < TEST_GRAPH_SIZE; source++) { - int numEdgesToCreate = - ((int) Math.random() * TEST_GRAPH_SIZE / 2) + 1; - - for (int j = 0; j < numEdgesToCreate; j++) { - // find a random vertex to join to - int target = (int) Math.floor(Math.random() * TEST_GRAPH_SIZE); - g.addEdge(vertices.get(source), vertices.get(target)); - } - } - - return g; - } -} - -// End VertexCoversTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/AllVariantsBronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/AllVariantsBronKerboschCliqueFinderTest.java new file mode 100644 index 00000000000..b02edac95b2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/AllVariantsBronKerboschCliqueFinderTest.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinderTest.java new file mode 100644 index 00000000000..c5e67535656 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BaseBronKerboschCliqueFinderTest.java @@ -0,0 +1,227 @@ +/* + * (C) Copyright 2005-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * . + * + * @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 + public void testNonSimple() + { + assertThrows(IllegalArgumentException.class, () -> { + 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())); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BronKerboschCliqueFinderTest.java new file mode 100644 index 00000000000..4751c97d6ff --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/BronKerboschCliqueFinderTest.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2005-2023, 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinderTest.java new file mode 100644 index 00000000000..b1e01c8e379 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/ChordalGraphMaxCliqueFinderTest.java @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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))); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition1.jpg b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition1.jpg new file mode 100644 index 00000000000..02c0ec99702 Binary files /dev/null and b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecomposition1.jpg differ diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecompositionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecompositionTest.java new file mode 100644 index 00000000000..56d1648ca28 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/CliqueMinimalSeparatorDecompositionTest.java @@ -0,0 +1,558 @@ +/* + * (C) Copyright 2015-2023, 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.generate.*; +import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for the Clique Minimal Separator Decomposition. + * + * @author Florian Buenzli {@literal } + */ +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(atomsFound[i], "atom " + 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(separatorsFound[i], "separator " + i); + + // check component counts + assertEquals(6, cmsd.getFullComponentCount().size()); + + assertEquals(2, cmsd.getFullComponentCount().get(Set.of("d", "e"))); + + assertEquals(2, cmsd.getFullComponentCount().get(Set.of("e"))); + + assertEquals(3, cmsd.getFullComponentCount().get(Set.of("d", "i"))); + + assertEquals(2, cmsd.getFullComponentCount().get(Set.of("i"))); + + assertEquals(2, cmsd.getFullComponentCount().get(Set.of("e", "j"))); + + assertEquals(3, cmsd.getFullComponentCount().get(Set.of("i", "j"))); + } + + /** + * 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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinderTest.java new file mode 100644 index 00000000000..2b34190be68 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/DegeneracyBronKerboschCliqueFinderTest.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2005-2023, 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinderTest.java new file mode 100644 index 00000000000..aca913206d9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clique/PivotBronKerboschCliqueFinderTest.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2005-2023, 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GirvanNewmanClusteringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GirvanNewmanClusteringTest.java new file mode 100644 index 00000000000..af0cdef3b18 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GirvanNewmanClusteringTest.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2021-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithmTest.java new file mode 100644 index 00000000000..9eaec32e1ae --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/GreedyModularityAlgorithmTest.java @@ -0,0 +1,499 @@ +/* + * (C) Copyright 2020-2021, by Antonia Tsiftsi and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.Set; +import org.jgrapht.Graph; +import org.jgrapht.alg.interfaces.ClusteringAlgorithm; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.builder.GraphTypeBuilder; +import org.jgrapht.util.SupplierUtil; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Tests + * + * @author Antonia Tsiftsi + */ +public class GreedyModularityAlgorithmTest +{ + @Test + public void test1() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + g.addVertex(1); + g.addVertex(2); + g.addVertex(3); + g.addVertex(4); + g.addVertex(5); + g.addVertex(6); + g.addVertex(7); + + g.addEdge(1, 2); + g.addEdge(2, 3); + g.addEdge(3, 1); + g.addEdge(3, 4); + g.addEdge(4, 5); + g.addEdge(4, 6); + g.addEdge(5, 6); + g.addEdge(6, 7); + g.addEdge(7, 5); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(2, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 3), clustering.getClusters().get(0)); + assertEquals(Set.of(4, 5, 6, 7), clustering.getClusters().get(1)); + } + + @Test + public void test2() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addVertex(15); + + g.addEdge(0, 2); + g.addEdge(0, 3); + g.addEdge(0, 4); + g.addEdge(0, 5); + g.addEdge(1, 2); + g.addEdge(1, 4); + g.addEdge(1, 7); + g.addEdge(2, 4); + g.addEdge(2, 5); + g.addEdge(2, 6); + g.addEdge(3, 7); + g.addEdge(4, 10); + g.addEdge(5, 7); + g.addEdge(5, 11); + g.addEdge(6, 7); + g.addEdge(6, 11); + g.addEdge(8, 9); + g.addEdge(8, 10); + g.addEdge(8, 11); + g.addEdge(8, 14); + g.addEdge(8, 15); + g.addEdge(9, 12); + g.addEdge(9, 14); + g.addEdge(10, 11); + g.addEdge(10, 12); + g.addEdge(10, 13); + g.addEdge(10, 14); + g.addEdge(11, 13); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(2, clustering.getNumberClusters()); + assertEquals(Set.of(0, 1, 2, 3, 4, 5, 6, 7), clustering.getClusters().get(0)); + assertEquals(Set.of(8, 9, 10, 11, 12, 13, 14, 15), clustering.getClusters().get(1)); + } + + @Test + public void testZacharyKarateClub() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addVertex(15); + g.addVertex(16); + g.addVertex(17); + g.addVertex(18); + g.addVertex(19); + g.addVertex(20); + g.addVertex(21); + g.addVertex(22); + g.addVertex(23); + g.addVertex(24); + g.addVertex(25); + g.addVertex(26); + g.addVertex(27); + g.addVertex(28); + g.addVertex(29); + g.addVertex(30); + g.addVertex(31); + g.addVertex(32); + g.addVertex(33); + g.addVertex(34); + + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(2, 3); + g.addEdge(1, 4); + g.addEdge(2, 4); + g.addEdge(3, 4); + g.addEdge(1, 5); + g.addEdge(1, 6); + g.addEdge(1, 7); + g.addEdge(5, 7); + g.addEdge(6, 7); + g.addEdge(1, 8); + g.addEdge(2, 8); + g.addEdge(3, 8); + g.addEdge(4, 8); + g.addEdge(1, 9); + g.addEdge(3, 9); + g.addEdge(3, 10); + g.addEdge(1, 11); + g.addEdge(5, 11); + g.addEdge(6, 11); + g.addEdge(1, 12); + g.addEdge(1, 13); + g.addEdge(4, 13); + g.addEdge(1, 14); + g.addEdge(2, 14); + g.addEdge(3, 14); + g.addEdge(4, 14); + g.addEdge(6, 17); + g.addEdge(7, 17); + g.addEdge(1, 18); + g.addEdge(2, 18); + g.addEdge(1, 20); + g.addEdge(2, 20); + g.addEdge(1, 22); + g.addEdge(2, 22); + g.addEdge(24, 26); + g.addEdge(25, 26); + g.addEdge(3, 28); + g.addEdge(24, 28); + g.addEdge(25, 28); + g.addEdge(3, 29); + g.addEdge(24, 30); + g.addEdge(27, 30); + g.addEdge(2, 31); + g.addEdge(9, 31); + g.addEdge(1, 32); + g.addEdge(25, 32); + g.addEdge(26, 32); + g.addEdge(29, 32); + g.addEdge(3, 33); + g.addEdge(9, 33); + g.addEdge(15, 33); + g.addEdge(16, 33); + g.addEdge(19, 33); + g.addEdge(21, 33); + g.addEdge(23, 33); + g.addEdge(24, 33); + g.addEdge(30, 33); + g.addEdge(31, 33); + g.addEdge(32, 33); + g.addEdge(9, 34); + g.addEdge(10, 34); + g.addEdge(14, 34); + g.addEdge(15, 34); + g.addEdge(16, 34); + g.addEdge(19, 34); + g.addEdge(20, 34); + g.addEdge(21, 34); + g.addEdge(23, 34); + g.addEdge(24, 34); + g.addEdge(27, 34); + g.addEdge(28, 34); + g.addEdge(29, 34); + g.addEdge(30, 34); + g.addEdge(31, 34); + g.addEdge(32, 34); + g.addEdge(33, 34); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(3, clustering.getNumberClusters()); + assertEquals(Set.of(2, 3, 4, 8, 10, 13, 14, 18, 22), clustering.getClusters().get(0)); + assertEquals(Set.of(1, 5, 6, 7, 11, 12, 17, 20), clustering.getClusters().get(1)); + assertEquals( + Set.of(32, 33, 34, 9, 15, 16, 19, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31), + clustering.getClusters().get(2)); + } + + @Test + public void test4() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addVertex(15); + g.addVertex(16); + + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 4); + g.addEdge(1, 5); + g.addEdge(1, 6); + g.addEdge(1, 8); + g.addEdge(1, 9); + g.addEdge(1, 11); + g.addEdge(1, 12); + g.addEdge(1, 14); + g.addEdge(1, 15); + g.addEdge(1, 16); + g.addEdge(2, 3); + g.addEdge(4, 5); + g.addEdge(6, 7); + g.addEdge(7, 8); + g.addEdge(9, 10); + g.addEdge(10, 11); + g.addEdge(12, 13); + g.addEdge(13, 14); + g.addEdge(15, 16); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(4, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 3, 4, 5, 15, 16), clustering.getClusters().get(0)); + assertEquals(Set.of(6, 7, 8), clustering.getClusters().get(1)); + assertEquals(Set.of(9, 10, 11), clustering.getClusters().get(2)); + assertEquals(Set.of(12, 13, 14), clustering.getClusters().get(3)); + } + + @Test + public void test5() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addEdge(1, 2); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + g.addEdge(3, 10); + g.addEdge(4, 5); + g.addEdge(5, 6); + g.addEdge(6, 7); + g.addEdge(7, 5); + g.addEdge(8, 5); + g.addEdge(8, 6); + g.addEdge(8, 10); + g.addEdge(9, 7); + g.addEdge(9, 10); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(2, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 3, 4), clustering.getClusters().get(0)); + assertEquals(Set.of(5, 6, 7, 8, 9, 10), clustering.getClusters().get(1)); + } + + @Test + public void test6() + { + Graph g = GraphTypeBuilder + .directed().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addEdge(1, 12); + g.addEdge(2, 3); + g.addEdge(3, 8); + g.addEdge(4, 3); + g.addEdge(4, 5); + g.addEdge(5, 7); + g.addEdge(6, 3); + g.addEdge(7, 9); + g.addEdge(8, 1); + g.addEdge(8, 2); + g.addEdge(8, 7); + g.addEdge(9, 1); + g.addEdge(9, 10); + g.addEdge(10, 6); + g.addEdge(10, 11); + g.addEdge(11, 9); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(4, clustering.getNumberClusters()); + assertEquals(Set.of(2, 3, 8), clustering.getClusters().get(0)); + assertEquals(Set.of(4, 5, 7), clustering.getClusters().get(1)); + assertEquals(Set.of(6, 9, 10, 11), clustering.getClusters().get(2)); + assertEquals(Set.of(1, 12), clustering.getClusters().get(3)); + } + + @Test + public void test7() + { + Graph g = GraphTypeBuilder + .directed().allowingSelfLoops(true).vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addEdge(1, 2); + g.addEdge(1, 10); + g.addEdge(3, 2); + g.addEdge(3, 8); + g.addEdge(4, 6); + g.addEdge(6, 3); + g.addEdge(6, 5); + g.addEdge(7, 6); + g.addEdge(8, 6); + g.addEdge(9, 3); + g.addEdge(9, 10); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(3, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 10), clustering.getClusters().get(0)); + assertEquals(Set.of(4, 5, 6, 7), clustering.getClusters().get(1)); + assertEquals(Set.of(3, 8, 9), clustering.getClusters().get(2)); + } + + @Test + public void test8() + { + Graph g = GraphTypeBuilder + .undirected().allowingSelfLoops(true) + .vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); + + 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.addEdge(1, 1); + g.addEdge(1, 2); + g.addEdge(1, 4); + g.addEdge(1, 6); + g.addEdge(2, 4); + g.addEdge(3, 4); + g.addEdge(5, 6); + g.addEdge(5, 5); + g.addEdge(6, 7); + g.addEdge(6, 11); + g.addEdge(7, 8); + g.addEdge(7, 10); + g.addEdge(8, 10); + g.addEdge(9, 10); + + ClusteringAlgorithm alg = new GreedyModularityAlgorithm<>(g); + ClusteringAlgorithm.Clustering clustering = alg.getClustering(); + + assertEquals(3, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 3, 4), clustering.getClusters().get(0)); + assertEquals(Set.of(5, 6, 11), clustering.getClusters().get(1)); + assertEquals(Set.of(7, 8, 9, 10), clustering.getClusters().get(2)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/KSpanningTreeClusteringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/KSpanningTreeClusteringTest.java new file mode 100644 index 00000000000..e2481652331 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/KSpanningTreeClusteringTest.java @@ -0,0 +1,176 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/LabelPropagationClusteringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/LabelPropagationClusteringTest.java new file mode 100644 index 00000000000..802f91df205 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/LabelPropagationClusteringTest.java @@ -0,0 +1,196 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithmTest.java new file mode 100644 index 00000000000..7e84d52fc65 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/NaiveGreedyModularityAlgorithmTest.java @@ -0,0 +1,373 @@ +/* + * (C) Copyright 2020-2021, by Antonia Tsiftsi and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.Set; +import org.jgrapht.Graph; +import org.jgrapht.alg.interfaces.ClusteringAlgorithm; +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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests + * + * @author Antonia Tsiftsi + */ +public class NaiveGreedyModularityAlgorithmTest +{ + @Test + public void test1() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).weighted(true) + .allowingSelfLoops(true).allowingMultipleEdges(true).buildGraph(); + + g.addVertex(1); + g.addVertex(2); + g.addVertex(3); + g.addVertex(4); + g.addVertex(5); + g.addVertex(6); + g.addVertex(7); + + g.addEdge(1, 2); + g.addEdge(2, 3); + g.addEdge(3, 1); + g.addEdge(3, 4); + g.addEdge(4, 5); + g.addEdge(4, 6); + g.addEdge(5, 6); + g.addEdge(6, 7); + g.addEdge(7, 5); + + ClusteringAlgorithm alg = new NaiveGreedyModularityAlgorithm<>(g); + Clustering clustering = alg.getClustering(); + + assertEquals(2, clustering.getNumberClusters()); + assertEquals(Set.of(1, 2, 3), clustering.getClusters().get(0)); + assertEquals(Set.of(4, 5, 6, 7), clustering.getClusters().get(1)); + } + + @Test + public void test2() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).weighted(true) + .allowingSelfLoops(true).allowingMultipleEdges(true).buildGraph(); + + 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.addVertex(15); + + g.addEdge(0, 2); + g.addEdge(0, 3); + g.addEdge(0, 4); + g.addEdge(0, 5); + g.addEdge(1, 2); + g.addEdge(1, 4); + g.addEdge(1, 7); + g.addEdge(2, 4); + g.addEdge(2, 5); + g.addEdge(2, 6); + g.addEdge(3, 7); + g.addEdge(4, 10); + g.addEdge(5, 7); + g.addEdge(5, 11); + g.addEdge(6, 7); + g.addEdge(6, 11); + g.addEdge(8, 9); + g.addEdge(8, 10); + g.addEdge(8, 11); + g.addEdge(8, 14); + g.addEdge(8, 15); + g.addEdge(9, 12); + g.addEdge(9, 14); + g.addEdge(10, 11); + g.addEdge(10, 12); + g.addEdge(10, 13); + g.addEdge(10, 14); + g.addEdge(11, 13); + + ClusteringAlgorithm alg = new NaiveGreedyModularityAlgorithm<>(g); + Clustering clustering = alg.getClustering(); + + assertEquals(2, clustering.getNumberClusters()); + assertEquals(Set.of(8, 9, 10, 11, 12, 13, 14, 15), clustering.getClusters().get(0)); + assertEquals(Set.of(0, 1, 2, 3, 4, 5, 6, 7), clustering.getClusters().get(1)); + } + + @Test + public void testZacharyKarateClub() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).weighted(true) + .allowingSelfLoops(true).allowingMultipleEdges(true).buildGraph(); + + 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.addVertex(15); + g.addVertex(16); + g.addVertex(17); + g.addVertex(18); + g.addVertex(19); + g.addVertex(20); + g.addVertex(21); + g.addVertex(22); + g.addVertex(23); + g.addVertex(24); + g.addVertex(25); + g.addVertex(26); + g.addVertex(27); + g.addVertex(28); + g.addVertex(29); + g.addVertex(30); + g.addVertex(31); + g.addVertex(32); + g.addVertex(33); + g.addVertex(34); + + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(2, 3); + g.addEdge(1, 4); + g.addEdge(2, 4); + g.addEdge(3, 4); + g.addEdge(1, 5); + g.addEdge(1, 6); + g.addEdge(1, 7); + g.addEdge(5, 7); + g.addEdge(6, 7); + g.addEdge(1, 8); + g.addEdge(2, 8); + g.addEdge(3, 8); + g.addEdge(4, 8); + g.addEdge(1, 9); + g.addEdge(3, 9); + g.addEdge(3, 10); + g.addEdge(1, 11); + g.addEdge(5, 11); + g.addEdge(6, 11); + g.addEdge(1, 12); + g.addEdge(1, 13); + g.addEdge(4, 13); + g.addEdge(1, 14); + g.addEdge(2, 14); + g.addEdge(3, 14); + g.addEdge(4, 14); + g.addEdge(6, 17); + g.addEdge(7, 17); + g.addEdge(1, 18); + g.addEdge(2, 18); + g.addEdge(1, 20); + g.addEdge(2, 20); + g.addEdge(1, 22); + g.addEdge(2, 22); + g.addEdge(24, 26); + g.addEdge(25, 26); + g.addEdge(3, 28); + g.addEdge(24, 28); + g.addEdge(25, 28); + g.addEdge(3, 29); + g.addEdge(24, 30); + g.addEdge(27, 30); + g.addEdge(2, 31); + g.addEdge(9, 31); + g.addEdge(1, 32); + g.addEdge(25, 32); + g.addEdge(26, 32); + g.addEdge(29, 32); + g.addEdge(3, 33); + g.addEdge(9, 33); + g.addEdge(15, 33); + g.addEdge(16, 33); + g.addEdge(19, 33); + g.addEdge(21, 33); + g.addEdge(23, 33); + g.addEdge(24, 33); + g.addEdge(30, 33); + g.addEdge(31, 33); + g.addEdge(32, 33); + g.addEdge(9, 34); + g.addEdge(10, 34); + g.addEdge(14, 34); + g.addEdge(15, 34); + g.addEdge(16, 34); + g.addEdge(19, 34); + g.addEdge(20, 34); + g.addEdge(21, 34); + g.addEdge(23, 34); + g.addEdge(24, 34); + g.addEdge(27, 34); + g.addEdge(28, 34); + g.addEdge(29, 34); + g.addEdge(30, 34); + g.addEdge(31, 34); + g.addEdge(32, 34); + g.addEdge(33, 34); + + ClusteringAlgorithm alg = new NaiveGreedyModularityAlgorithm<>(g); + Clustering clustering = alg.getClustering(); + + assertEquals(3, clustering.getNumberClusters()); + assertEquals(Set.of(2, 3, 4, 8, 10, 13, 14, 18, 22), clustering.getClusters().get(0)); + assertEquals( + Set.of(32, 33, 34, 9, 15, 16, 19, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31), + clustering.getClusters().get(1)); + assertEquals(Set.of(1, 5, 6, 7, 11, 12, 17, 20), clustering.getClusters().get(2)); + } + + @Test + public void test4() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).weighted(true) + .allowingSelfLoops(true).allowingMultipleEdges(true).buildGraph(); + + 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.addVertex(15); + g.addVertex(16); + + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 4); + g.addEdge(1, 5); + g.addEdge(1, 6); + g.addEdge(1, 8); + g.addEdge(1, 9); + g.addEdge(1, 11); + g.addEdge(1, 12); + g.addEdge(1, 14); + g.addEdge(1, 15); + g.addEdge(1, 16); + g.addEdge(2, 3); + g.addEdge(4, 5); + g.addEdge(6, 7); + g.addEdge(7, 8); + g.addEdge(9, 10); + g.addEdge(10, 11); + g.addEdge(12, 13); + g.addEdge(13, 14); + g.addEdge(15, 16); + + ClusteringAlgorithm alg = new NaiveGreedyModularityAlgorithm<>(g); + Clustering clustering = alg.getClustering(); + + assertEquals(4, clustering.getNumberClusters()); + assertEquals(Set.of(12, 13, 14), clustering.getClusters().get(0)); + assertEquals(Set.of(9, 10, 11), clustering.getClusters().get(1)); + assertEquals(Set.of(6, 7, 8), clustering.getClusters().get(2)); + assertEquals(Set.of(1, 2, 3, 4, 5, 15, 16), clustering.getClusters().get(3)); + } + + @Test + public void test5() + { + Graph g = GraphTypeBuilder + .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).weighted(true) + .allowingSelfLoops(true).allowingMultipleEdges(true).buildGraph(); + + 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.addEdge(1, 2); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + g.addEdge(3, 10); + g.addEdge(4, 5); + g.addEdge(5, 6); + g.addEdge(6, 7); + g.addEdge(7, 5); + g.addEdge(8, 5); + g.addEdge(8, 6); + g.addEdge(8, 10); + g.addEdge(9, 7); + g.addEdge(9, 10); + + ClusteringAlgorithm alg = new NaiveGreedyModularityAlgorithm<>(g); + Clustering clustering = alg.getClustering(); + + assertEquals(3, clustering.getNumberClusters()); + assertEquals(Set.of(9, 10), clustering.getClusters().get(0)); + assertEquals(Set.of(1, 2, 3, 4), clustering.getClusters().get(1)); + assertEquals(Set.of(5, 6, 7, 8), clustering.getClusters().get(2)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurerTest.java new file mode 100644 index 00000000000..2ac328a777e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/UndirectedModularityMeasurerTest.java @@ -0,0 +1,366 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Set; + +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.builder.GraphTypeBuilder; +import org.jgrapht.util.SupplierUtil; +import org.junit.jupiter.api.Test; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class UndirectedModularityMeasurerTest +{ + + @Test + public void testOptimalPartition() + { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of(Set.of(0, 1, 2, 3, 4), Set.of(5, 6, 7, 8)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, 0.4112426, 1e-6); + + } + + @Test + public void testSingle() + { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of(Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, 0.0, 1e-6); + + } + + @Test + public void testSuboptimal() + { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of(Set.of(0, 3, 4), Set.of(1, 2, 5, 6, 7, 8)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, 0.118343, 1e-6); + + } + + @Test + public void testNegative() + { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of( + Set.of(0), Set.of(3), Set.of(4), Set.of(1), Set.of(2), Set.of(5), Set.of(6), Set.of(7), + Set.of(8)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, -0.118343, 1e-6); + + } + + @Test + public void test24() + { + 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 < 16; i++) { + g.addVertex(i); + } + + g.addEdge(0, 1); + g.addEdge(0, 2); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(2, 5); + g.addEdge(2, 8); + g.addEdge(3, 4); + g.addEdge(3, 6); + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(6, 7); + g.addEdge(8, 9); + g.addEdge(8, 10); + g.addEdge(9, 10); + g.addEdge(9, 11); + g.addEdge(10, 11); + g.addEdge(11, 12); + g.addEdge(12, 13); + g.addEdge(12, 14); + g.addEdge(13, 14); + g.addEdge(13, 15); + g.addEdge(14, 15); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of( + Set.of(0, 1, 2, 3, 4), Set.of(5, 6, 7), Set.of(8, 9, 10, 11), Set.of(12, 13, 14, 15)); + double mod = measurer.modularity(partitions); + + assertEquals(0.565104, mod, 1e-6); + + } + + @Test + public void testWithInvalidPartition() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of(Set.of(0, 3, 4), Set.of(1, 2, 5, 7, 8, 9)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, 0.118343, 1e-6); + }); + } + + @Test + public void testWithSelfLoops() + { + Graph g = GraphTypeBuilder + .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) + .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) + .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); + + 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.addEdge(0, 1); + g.addEdge(0, 4); + g.addEdge(1, 2); + g.addEdge(1, 3); + g.addEdge(1, 5); + g.addEdge(2, 3); + g.addEdge(2, 4); + g.addEdge(3, 4); + + g.addEdge(5, 6); + g.addEdge(5, 7); + g.addEdge(5, 8); + g.addEdge(6, 7); + g.addEdge(7, 8); + + // add self-loops + g.addEdge(4, 4); + g.addEdge(7, 7); + + UndirectedModularityMeasurer measurer = + new UndirectedModularityMeasurer<>(g); + + List> partitions = List.of(Set.of(0, 1, 2, 3, 4), Set.of(5, 6, 7, 8)); + double mod = measurer.modularity(partitions); + + assertEquals(mod, 0.42444444444444446, 1e-6); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/BaseColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/BaseColoringTest.java new file mode 100644 index 00000000000..dfc97966469 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/BaseColoringTest.java @@ -0,0 +1,337 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/BrownBacktrackColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/BrownBacktrackColoringTest.java new file mode 100644 index 00000000000..6538ddbe2ef --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/BrownBacktrackColoringTest.java @@ -0,0 +1,523 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 + @Tag("optional") + 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 + @Tag("optional") + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/ChordalGraphColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/ChordalGraphColoringTest.java new file mode 100644 index 00000000000..9137a10c8e7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/ChordalGraphColoringTest.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + } + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/ColorRefinementAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/ColorRefinementAlgorithmTest.java new file mode 100644 index 00000000000..da955b847da --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/ColorRefinementAlgorithmTest.java @@ -0,0 +1,188 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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), colors.get(5)); + assertNotEquals(colors.get(1), colors.get(7)); + assertEquals(colors.get(2), colors.get(4)); + assertEquals(colors.get(7), colors.get(8)); + assertNotEquals(colors.get(1), colors.get(2)); + assertNotEquals(colors.get(2), colors.get(3)); + assertNotEquals(colors.get(3), colors.get(6)); + } + + @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), colors.get(2)); + assertEquals(colors.get(1), colors.get(3)); + assertEquals(colors.get(1), colors.get(4)); + assertEquals(colors.get(1), colors.get(5)); + assertEquals(colors.get(1), colors.get(6)); + } + + @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), colors.get(j)); + } + } + } + assertEquals(colors.get(9), colors.get(10)); + } + + @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), colors.get(j)); + } else { + assertNotEquals(colors.get(i), colors.get(j)); + } + } + } + assertEquals(colors.get(1), colors.get(3)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/GreedyColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/GreedyColoringTest.java new file mode 100644 index 00000000000..a60b8aa2680 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/GreedyColoringTest.java @@ -0,0 +1,66 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + assertEquals(1, colors.get(2)); + assertEquals(2, colors.get(3)); + assertEquals(1, colors.get(4)); + assertEquals(1, colors.get(5)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/LargestDegreeFirstColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/LargestDegreeFirstColoringTest.java new file mode 100644 index 00000000000..ce1a7559abd --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/LargestDegreeFirstColoringTest.java @@ -0,0 +1,154 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + assertEquals(2, colors.get(2)); + assertEquals(1, colors.get(3)); + assertEquals(2, colors.get(4)); + assertEquals(2, colors.get(5)); + } + + @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)); + assertEquals(2, colors.get(2)); + assertEquals(0, colors.get(3)); + assertEquals(2, colors.get(4)); + assertEquals(2, colors.get(5)); + assertEquals(1, colors.get(6)); + } + + @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)); + assertEquals(0, colors.get(2)); + assertEquals(1, colors.get(3)); + assertEquals(2, colors.get(4)); + assertEquals(0, colors.get(5)); + assertEquals(1, colors.get(6)); + } + + @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)); + assertEquals(0, colors.get(2)); + assertEquals(1, colors.get(3)); + assertEquals(1, colors.get(4)); + assertEquals(0, colors.get(5)); + assertEquals(2, colors.get(6)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoring2Test.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoring2Test.java new file mode 100644 index 00000000000..03af4709d6e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoring2Test.java @@ -0,0 +1,49 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoringTest.java new file mode 100644 index 00000000000..eda1ef095be --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoringTest.java @@ -0,0 +1,49 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/SaturationDegreeColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/SaturationDegreeColoringTest.java new file mode 100644 index 00000000000..e9a4f96d5d9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/SaturationDegreeColoringTest.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + assertEquals(0, colors.get(2)); + assertEquals(1, colors.get(3)); + assertEquals(1, colors.get(4)); + assertEquals(0, colors.get(5)); + assertEquals(2, colors.get(6)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/SmallestDegreeLastColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/SmallestDegreeLastColoringTest.java new file mode 100644 index 00000000000..fc3652f0418 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/SmallestDegreeLastColoringTest.java @@ -0,0 +1,94 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + assertEquals(0, colors.get(2)); + assertEquals(1, colors.get(3)); + assertEquals(0, colors.get(4)); + assertEquals(0, colors.get(5)); + } + + @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)); + assertEquals(1, colors.get(2)); + assertEquals(0, colors.get(3)); + assertEquals(2, colors.get(4)); + assertEquals(1, colors.get(5)); + assertEquals(0, colors.get(6)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectedGraph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectedGraph.java new file mode 100644 index 00000000000..35cfa3e782e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectedGraph.java @@ -0,0 +1,64 @@ +/* + * (C) Copyright 2007-2023, 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"); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectivityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectivityInspectorTest.java new file mode 100644 index 00000000000..bf2c3ef6017 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectivityInspectorTest.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2007-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @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(); + assertEquals(1, cutpoints.size()); + assertTrue(cutpoints.contains(0)); + + Set bridges = bi.getBridges(); + assertEquals(1, bridges.size()); + assertTrue(bridges.contains(e03)); + + assertEquals(2, bi.getBlocks(0).size()); + assertEquals(1, bi.getBlocks(1).size()); + assertEquals(1, bi.getBlocks(2).size()); + assertEquals(1, bi.getBlocks(3).size()); + assertEquals(2, bi.getBlocks().size()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BlockCutpointGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BlockCutpointGraphTest.java new file mode 100644 index 00000000000..7cf6b22fbfc --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BlockCutpointGraphTest.java @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + } + + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/ConnectivityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/ConnectivityInspectorTest.java new file mode 100644 index 00000000000..c2fc7aae98f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/ConnectivityInspectorTest.java @@ -0,0 +1,146 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * . + * + * @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 e3B; + 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()); + + e3B = g.addEdge(V3, V1); + assertEquals(4, g.edgeSet().size()); + assertNotNull(e3B); + + 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); + + assertFalse(inspector.isConnected()); + + g.addEdge(V1, V3); + + assertTrue(inspector.isConnected()); + } + + /** + * . + */ + @Test + public void testIsGraphConnected() + { + Pseudograph g = create(); + ConnectivityInspector inspector = new ConnectivityInspector<>(g); + + assertFalse(inspector.isConnected()); + + g.removeVertex(V4); + inspector = new ConnectivityInspector<>(g); + assertTrue(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); + assertFalse(inspector.isConnected()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/NotBiconnectedGraph.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/NotBiconnectedGraph.java new file mode 100644 index 00000000000..6e459032f8d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/NotBiconnectedGraph.java @@ -0,0 +1,64 @@ +/* + * (C) Copyright 2007-2023, 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"); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/StrongConnectivityAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/StrongConnectivityAlgorithmTest.java new file mode 100644 index 00000000000..5308c06f17c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/StrongConnectivityAlgorithmTest.java @@ -0,0 +1,421 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +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.jupiter.api.Assertions.*; + +/** + * 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 + */ +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 -------------------------------------------------------- + + @SuppressWarnings("unchecked") + public static List getAlgorithmFactory() + { + return List.of( + Arguments.of(GabowStrongConnectivityInspector.class.getSimpleName(), + (Function, ?>) GabowStrongConnectivityInspector::new), + + Arguments.of(KosarajuStrongConnectivityInspector.class.getSimpleName(), + (Function, ?>) KosarajuStrongConnectivityInspector::new )); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected1(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + Graph g = createDirectedGraphWithVertices(4); + + g.addEdge(V1, V2); + g.addEdge(V2, V1); // strongly connected + + g.addEdge(V3, V4); // only weakly connected + + assertStronglyConnectedSets(g, algorithmFactory, Set.of(V1, V2), Set.of(V3), Set.of(V4)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected2(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory, Set.of(V1, V2), Set.of(V3), Set.of(V4)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected3(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory, Set.of(V1, V2, V3), Set.of(V4)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected4(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + Graph graph = new DefaultDirectedGraph<>( + SupplierUtil.createIntegerSupplier(), SupplierUtil.createStringSupplier(), false); + + new RingGraphGenerator(3).generateGraph(graph); + + assertStronglyConnectedSets(graph, algorithmFactory, Set.of(0, 1, 2)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected5(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + + // 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, algorithmFactory, Set.of(V1), Set.of(V2, V4, V5, V6), Set.of(V3)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected6(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + // 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, algorithmFactory, Set.of(V1, V2, V5), Set.of(V3, V4), Set.of(V6, V7), Set.of(V8)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected7(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory, Set.of(V1, V2, V3, V4, V5)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + public void testStronglyConnected8(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory, Set.of(V1, V2, V3, V4), Set.of(V5, V6, V7), Set.of(V8), Set.of(V9, V10), + Set.of(V11)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + @SuppressWarnings("unchecked") + public void testCondensation(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory); + + 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))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("getAlgorithmFactory") + @SuppressWarnings("unchecked") + public void testCondensation2(String name, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + 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, algorithmFactory); + + 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, Function, StrongConnectivityAlgorithm> algorithmFactory, 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, algorithmFactory); + + assertThat(inspector.stronglyConnectedSets(), containsInAnyOrder(expectedVertices)); + + Set> actualSets = new HashSet<>(); + for (Graph sg : inspector.getStronglyConnectedComponents()) { + actualSets.add(sg.vertexSet()); + + StrongConnectivityAlgorithm ci = getStrongConnectivityInspector(sg, algorithmFactory); + 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, Function, StrongConnectivityAlgorithm> algorithmFactory) + { + return (StrongConnectivityAlgorithm) algorithmFactory.apply(g); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivityTest.java new file mode 100644 index 00000000000..ca1c92410c0 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/TreeDynamicConnectivityTest.java @@ -0,0 +1,142 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentationTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentationTest.java new file mode 100644 index 00000000000..1b4aaf5d573 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/AhujaOrlinSharmaCyclicExchangeLocalAugmentationTest.java @@ -0,0 +1,589 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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(0, calculatedCycle1.getStartVertex()); + assertEquals(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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/BergeGraphInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/BergeGraphInspectorTest.java new file mode 100644 index 00000000000..1f558a74f26 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/BergeGraphInspectorTest.java @@ -0,0 +1,647 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class BergeGraphInspectorTest +{ + private SimpleGraph stimulus; + private BergeGraphInspector dut; + + @BeforeEach + public void reset() + { + stimulus = new SimpleGraph<>( + SupplierUtil.createIntegerSupplier(), SupplierUtil.createIntegerSupplier(), false); + dut = new BergeGraphInspector<>(); + } + + 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() + { + 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() + { + 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() + { + 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() + { + 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() + { + 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() + { + 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() + { + 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() + { + 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() + { + new NamedGraphGenerator().generatePetersenGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkDodecahedronGraph() + { + new NamedGraphGenerator().generateDodecahedronGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("optional") + public void checkMoebiusKantorGraph() + { + new NamedGraphGenerator().generateMöbiusKantorGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkBullGraph() + { + new NamedGraphGenerator().generateBullGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkButterflyGraph() + { + new NamedGraphGenerator().generateButterflyGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkClawGraph() + { + new NamedGraphGenerator().generateClawGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkGroetzschGraph() + { + new NamedGraphGenerator().generateGrötzschGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkDiamondGraph() + { + new NamedGraphGenerator().generateDiamondGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("slow") + public void checkFranklinGraph() + { + 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 + @Tag("slow") + public void checkGoldnerHararyGraph() + { + new NamedGraphGenerator().generateGoldnerHararyGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("slow") + public void checkHeawoodGraph() + { + new NamedGraphGenerator().generateHeawoodGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("slow") + public void checkHerschelGraph() + { + new NamedGraphGenerator().generateHerschelGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("slow") + public void checkKrackhardtKiteGraph() + { + new NamedGraphGenerator().generateKrackhardtKiteGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkMoserSpindleGraph() + { + new NamedGraphGenerator().generateMoserSpindleGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("optional") + public void checkPappusGraph() + { + new NamedGraphGenerator().generatePappusGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkTietzeGraph() + { + new NamedGraphGenerator().generateTietzeGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkThomsenGraph() + { + new NamedGraphGenerator().generateThomsenGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkTutteGraph() + { + new NamedGraphGenerator().generateTutteGraph(stimulus); + assertFalse(dut.isBerge(stimulus, true)); + assertTrue(verifyCertificate(dut.getCertificate())); + } + + @Test + public void checkEmptyGraph() + { + int numberOfVertices = + new Random().nextInt(maximalNumberOfVertices - minimalNumberOfVertices) + + minimalNumberOfVertices; + new EmptyGraphGenerator(numberOfVertices).generateGraph(stimulus); + assertTrue(dut.isBerge(stimulus, true)); + assertFalse(verifyCertificate(dut.getCertificate())); + } + + @Test + @Tag("optional") + public void checkBipartiteGraphs() + { + int repititions = repititionsPerTestCase; + 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 + @Tag("optional") + 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 + @Tag("optional") + 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)); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChinesePostmanTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChinesePostmanTest.java new file mode 100644 index 00000000000..e0f877f2096 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChinesePostmanTest.java @@ -0,0 +1,356 @@ +/* + * (C) Copyright 2017-2023, 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 static org.junit.jupiter.api.Assertions.*; + +import org.jgrapht.*; +import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +import java.util.*; + +/** + * @author Joris Kinable + */ +public class ChinesePostmanTest +{ + + @Test + public void testGraphNoVertices() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + alg.getCPPSolution(g); + } + + private void verifyClosedPath(Graph graph, double expectedWeight, int expectedLength) + { + + ChinesePostman alg = new ChinesePostman<>(); + GraphPath path = alg.getCPPSolution(graph); + + assertEquals(expectedLength, path.getLength()); + assertEquals(expectedLength, path.getEdgeList().size()); + assertEquals(expectedWeight, path.getWeight(), 0.00000001); + assertEquals( + expectedWeight, path.getEdgeList().stream().mapToDouble(graph::getEdgeWeight).sum(), + 0.00000001); + + // all edges of the graph must be visited at least once + assertTrue(path.getEdgeList().containsAll(graph.edgeSet())); + + assertTrue(graph.containsVertex(path.getStartVertex())); + assertEquals(path.getStartVertex(), path.getEndVertex()); + + // Verify that the path is an actual path in the graph + assertEquals(path.getEdgeList().size() + 1, path.getVertexList().size()); + List vertexList = path.getVertexList(); + List edgeList = path.getEdgeList(); + + // Check start and end vertex + assertEquals(vertexList.get(0), path.getStartVertex()); + assertEquals(vertexList.get(vertexList.size() - 1), path.getEndVertex()); + + // All vertices and edges in the path must be contained in the graph + assertTrue(graph.vertexSet().containsAll(vertexList)); + 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()) { + assertEquals(Graphs.getOppositeVertex(graph, edge, u), v); + } else { // Directed + assertEquals(graph.getEdgeSource(edge), u); + assertEquals(graph.getEdgeTarget(edge), v); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinderTest.java new file mode 100644 index 00000000000..c62e1b60985 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalGraphMinimalVertexSeparatorFinderTest.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalityInspectorTest.java new file mode 100644 index 00000000000..a6f64927eb9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalityInspectorTest.java @@ -0,0 +1,415 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link ChordalityInspector} + * + * @author Timofey Chudakov + */ +public class ChordalityInspectorTest +{ + /** + * Tests usage of the correct iteration order + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIterationOrder(ChordalityInspector.IterationOrder iterationOrder) + { + 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. + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testPerfectEliminationOrder(ChordalityInspector.IterationOrder iterationOrder) + { + 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 + public void testUnmodifiableList() + { + assertThrows(UnsupportedOperationException.class, () -> { + 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 schlaefli = NamedGraphGenerator.schläfliGraph(); + ChordalityInspector inspector = new ChordalityInspector<>(schlaefli); + assertFalse(inspector.isChordal()); + assertIsHole(schlaefli, 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)} + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder1(ChordalityInspector.IterationOrder iterationOrder) + { + 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 + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder2(ChordalityInspector.IterationOrder iterationOrder) + { + 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( + inspector.isPerfectEliminationOrder(order), + "Not a perfect elimination order: cycle 1->2->3->4->1 has non chord"); + graph.addEdge(2, 4); + assertTrue( + inspector.isPerfectEliminationOrder(order), + "Valid perfect elimination order: no induced cycles of length > 3"); + } + + /** + * Test on chordal graph:
    + * .......5
    + * ...../.|.\
    + * ....4--3--6--7
    + * ....|./.|.|\.|
    + * ....1--2..9--8
    + * ...........\.|
    + * ............10
    + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder3(ChordalityInspector.IterationOrder iterationOrder) + { + 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 + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder4(ChordalityInspector.IterationOrder iterationOrder) + { + 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( + new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order), + "Valid perfect elimination order"); + } + + /** + * Test on chordal graph with invalid perfect elimination order + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder5(ChordalityInspector.IterationOrder iterationOrder) + { + 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( + new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order), + "Graph is chordal, order isn't perfect elimination order"); + } + + /** + * Test on graph with 5-vertex cycle 2-4-6-8-10-2 with no chords + */ + @ParameterizedTest + @EnumSource(ChordalityInspector.IterationOrder.class) + public void testIsPerfectEliminationOrder6(ChordalityInspector.IterationOrder iterationOrder) + { + 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( + new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order), + "Cycle 2->4->6->8->10->2 has no chords => no perfect elimination 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))); + } + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CycleDetectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CycleDetectorTest.java new file mode 100644 index 00000000000..0d93793565d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CycleDetectorTest.java @@ -0,0 +1,204 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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) { + assertTrue(detector.detectCyclesContainingVertex(v)); + assertEquals(cyclicSet, detector.findCyclesContainingVertex(v)); + } + + for (String v : acyclicSet) { + assertFalse(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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CyclesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CyclesTest.java new file mode 100644 index 00000000000..a02348d3e96 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CyclesTest.java @@ -0,0 +1,156 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testUndirectedNotSimple1() + { + assertThrows(IllegalArgumentException.class, () -> { + 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))); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/DirectedSimpleCyclesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/DirectedSimpleCyclesTest.java new file mode 100644 index 00000000000..00c619935c3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/DirectedSimpleCyclesTest.java @@ -0,0 +1,134 @@ +/* + * (C) Copyright 2013-2023, 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.jupiter.api.*; + +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DirectedSimpleCyclesTest +{ + private static final int MAX_SIZE = 9; + private static final 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); + assertEquals(1, alg.findSimpleCycles().size()); + graph.addEdge(1, 1); + assertEquals(2, alg.findSimpleCycles().size()); + graph.addEdge(0, 1); + graph.addEdge(1, 0); + assertEquals(3, alg.findSimpleCycles().size()); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 0); + assertEquals(4, alg.findSimpleCycles().size()); + graph.addEdge(6, 6); + assertEquals(5, alg.findSimpleCycles().size()); + + 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); + assertEquals(RESULTS[size], alg.findSimpleCycles().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); + assertEquals(1, alg.findSimpleCycles().size()); + graph.addEdge(1, 1); + assertEquals(2, alg.findSimpleCycles().size()); + graph.addEdge(0, 1); + graph.addEdge(1, 0); + assertEquals(3, alg.findSimpleCycles().size()); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 0); + assertEquals(4, alg.findSimpleCycles().size()); + graph.addEdge(6, 6); + assertEquals(5, alg.findSimpleCycles().size()); + + 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); + assertEquals(RESULTS[size], alg.findSimpleCycles().size()); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HawickJamesSimpleCyclesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HawickJamesSimpleCyclesTest.java new file mode 100644 index 00000000000..a852ea61415 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HawickJamesSimpleCyclesTest.java @@ -0,0 +1,383 @@ +/* + * (C) Copyright 2013-2023, 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.jupiter.api.*; + +import java.util.*; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 = String.join(",", cycles.get(0)); + String cycle1 = String.join(",", cycles.get(1)); + + assertEquals("0,1,2,3", cycle0); + assertEquals("0,1,4,5,2,3", cycle1); + } + + @Test + public void testConsumerAPI() + { + SampleVertex vertexA = new SampleVertex("A", true); + SampleVertex vertexB = new SampleVertex("B", true); + SampleVertex vertexC = new SampleVertex("C", false); + + Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); + graph.addVertex(vertexA); + graph.addVertex(vertexB); + graph.addVertex(vertexC); + + graph.addEdge(vertexA, vertexB); + graph.addEdge(vertexB, vertexA); + graph.addEdge(vertexB, vertexC); + graph.addEdge(vertexC, vertexA); + + List> invalidCycles = new ArrayList<>(); + + new HawickJamesSimpleCycles<>(graph).findSimpleCycles(cycle -> { + if (cycle.stream().anyMatch(vertex -> !vertex.allowedInCycles)) { + invalidCycles.add(cycle); + } + }); + + assertEquals(1, invalidCycles.size()); + assertEquals(asList(vertexA, vertexB, vertexC), invalidCycles.get(0)); + } + + static class SampleVertex + { + final String name; + final boolean allowedInCycles; + + SampleVertex(String name, boolean allowedInCycles) + { + this.name = name; + this.allowedInCycles = allowedInCycles; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HierholzerEulerianCycleTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HierholzerEulerianCycleTest.java new file mode 100644 index 00000000000..89ae56ca9f9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HierholzerEulerianCycleTest.java @@ -0,0 +1,804 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for class {@link HierholzerEulerianCycle}. + * + * @author Dimitrios Michail + */ +public class HierholzerEulerianCycleTest +{ + @Test + public void testNullEulerian() + { + Graph g1 = new Pseudograph<>(DefaultEdge.class); + assertFalse(new HierholzerEulerianCycle().isEulerian(g1)); + + Graph g2 = new DirectedPseudograph<>(DefaultEdge.class); + assertFalse(new HierholzerEulerianCycle().isEulerian(g2)); + } + + @Test + public void testEmptyEulerian() + { + Graph g1 = new Pseudograph<>(DefaultEdge.class); + g1.addVertex(1); + assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); + g1.addVertex(2); + assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); + g1.addVertex(3); + assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); + + Graph g2 = new DirectedPseudograph<>(DefaultEdge.class); + g2.addVertex(1); + assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); + g2.addVertex(2); + assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); + g2.addVertex(3); + 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); + + 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); + + 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); + + 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); + + 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); + 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); + 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); + assertTrue(new HierholzerEulerianCycle().isEulerian(g)); + } + + @Test + public void testUndirectedEulerian4() + { + // even degrees + Graph g = new Pseudograph<>(DefaultEdge.class); + g.addVertex(1); + 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)); + 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)); + 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); + 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); + 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); + assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); + } + + @Test + public void testDirectedEulerian2() + { + Graph g = new DirectedPseudograph<>(DefaultEdge.class); + Graphs.addAllVertices(g, Arrays.asList(1)); + assertTrue(new HierholzerEulerianCycle().isEulerian(g)); + g.addEdge(1, 1); + g.addEdge(1, 1); + assertTrue(new HierholzerEulerianCycle().isEulerian(g)); + Graphs.addAllVertices(g, Arrays.asList(2)); + g.addEdge(2, 1); + 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)); + 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); + assertTrue(new HierholzerEulerianCycle().isEulerian(g)); + g.addEdge(2, 1); + g.addEdge(3, 2); + g.addEdge(4, 3); + g.addEdge(5, 4); + 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); + assertFalse(new HierholzerEulerianCycle().isEulerian(g)); + g.addEdge(1, 5); + 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)); + assertEquals(3, vl.get(1)); + assertEquals(4, vl.get(2)); + assertEquals(2, vl.get(3)); + + 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 + fail(); + } else if (GraphTests.isEmpty(g)) { + assertNull(cycle.getStartVertex()); + assertNull(cycle.getEndVertex()); + 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; + } + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycleTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycleTest.java new file mode 100644 index 00000000000..fdfb55f4d8a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycleTest.java @@ -0,0 +1,200 @@ +/* + * (C) Copyright 2020-2023, 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.DirectedWeightedPseudograph; +import org.jgrapht.graph.GraphWalk; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/JohnsonSimpleCyclesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/JohnsonSimpleCyclesTest.java new file mode 100644 index 00000000000..a1fdd84465f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/JohnsonSimpleCyclesTest.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * 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(); + + assertEquals(2, cycles.size()); + + List cycle0 = cycles.get(0); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), cycle0); + + List cycle1 = cycles.get(1); + assertEquals(Arrays.asList(1, 2, 5, 6), cycle1); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/PatonCycleBaseTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/PatonCycleBaseTest.java new file mode 100644 index 00000000000..e095c890d70 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/PatonCycleBaseTest.java @@ -0,0 +1,628 @@ +/* + * (C) Copyright 2013-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PatonCycleBaseTest +{ + private static final int MAX_SIZE = 10; + private static final 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) + { + assertEquals(size, finder.getCycleBasis().getCycles().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 + public void testMultipleEdges() + { + assertThrows(IllegalArgumentException.class, () -> { + 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))); + } + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasisTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasisTest.java new file mode 100644 index 00000000000..c1e95872bc7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/QueueBFSFundamentalCycleBasisTest.java @@ -0,0 +1,568 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasisTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasisTest.java new file mode 100644 index 00000000000..03701617cc7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/StackBFSFundamentalCycleBasisTest.java @@ -0,0 +1,643 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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))); + } + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/WeakChordalityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/WeakChordalityInspectorTest.java new file mode 100644 index 00000000000..19bf1cd8d2b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/WeakChordalityInspectorTest.java @@ -0,0 +1,469 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 groetzsch = NamedGraphGenerator.grötzschGraph(); + WeakChordalityInspector inspector = + new WeakChordalityInspector<>(groetzsch); + assertFalse(inspector.isWeaklyChordal()); + GraphPath graphPath = inspector.getCertificate(); + assertNotNull(graphPath); + assertIsHoleOrAntiHole(groetzsch, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecompositionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecompositionTest.java new file mode 100644 index 00000000000..eecce9d9c88 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/DulmageMendelsohnDecompositionTest.java @@ -0,0 +1,189 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test for Dulmage-Mendelsohn, based on MaximumCardinailityBipartiteMatchingTest + * + * @author Peter Harman + * @author Joris Kinable + */ +public class DulmageMendelsohnDecompositionTest +{ + + public static List> generators() + { + List> 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(generator); + } + return out; + } + + @ParameterizedTest + @MethodSource("generators") + public void testGeneratedGraph(GnmRandomBipartiteGraphGenerator generator) + { + 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(perfectMatching.isPerfect(), "Core of decomposition must perfectly match"); + // 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( + decomposition.getPartition1DominatedSet().contains(v), + "Vertex appears in multiple sets in decomposition"); + assertFalse( + decomposition.getPartition2DominatedSet().contains(v), + "Vertex appears in multiple sets in decomposition"); + } else if (decomposition.getPartition1DominatedSet().contains(v)) { + assertFalse( + allPerfectlyMatched.contains(v), + "Vertex appears in multiple sets in decomposition"); + assertFalse( + decomposition.getPartition2DominatedSet().contains(v), + "Vertex appears in multiple sets in decomposition"); + } else { + assertTrue( + decomposition.getPartition2DominatedSet().contains(v), + "Vertex appears in multiple sets in decomposition"); + } + } + ; + // 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( + n1 > n2 || (n1 == 0 && n2 == 0), + "Partition 1 dominated set is not dominated by partition 1"); + n1 = 0; + n2 = 0; + for (V v : decomposition.getPartition2DominatedSet()) { + if (partition1.contains(v)) { + n1++; + } else { + n2++; + } + } + assertTrue( + n1 < n2 || (n1 == 0 && n2 == 0), + "Partition 2 dominated set is not dominated by partition 2"); + } + + /** + * 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/HeavyPathDecompositionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/HeavyPathDecompositionTest.java new file mode 100644 index 00000000000..81eaed1466d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/HeavyPathDecompositionTest.java @@ -0,0 +1,411 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.jgrapht.util.MathUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testNullGraph() + { + assertThrows(NullPointerException.class, () -> new HeavyPathDecomposition<>(null, 1)); + } + + @Test + public void testNullRoot() + { + assertThrows(NullPointerException.class, () -> { + Graph graph = new SimpleGraph<>(DefaultEdge.class); + String s = null; + + new HeavyPathDecomposition<>(graph, s); + }); + } + + @Test + public void testRootNotInTree() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = new SimpleGraph<>(DefaultEdge.class); + graph.addVertex("a"); + + 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"); + + assertTrue(heavyPathDecomposition.getHeavyEdges().isEmpty()); + 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"); + + assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); + 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); + + assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); + 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); + + assertEquals(2, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); + 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); + + 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); + + assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); + assertTrue(heavyPathDecomposition.getHeavyEdges().isEmpty()); + assertEquals(1, heavyPathDecomposition.getLightEdges().size()); + } + + @Test + @Tag("slow") + public void testRandomTrees() + { + final int numTests = 100; + Random random = new Random(0x2882); + + for (int test = 0; test < numTests; 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); + + assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); + } + } + + @Test + @Tag("slow") + public void testRandomForests() + { + final int numTests = 1000; + Random random = new Random(0x1881); + + for (int test = 0; test < numTests; 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); + + assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); + } + } + + @Test + @Tag("slow") + 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); + + assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsPerEdgeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsPerEdgeTest.java new file mode 100644 index 00000000000..437eb9926fc --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsPerEdgeTest.java @@ -0,0 +1,173 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsTest.java new file mode 100644 index 00000000000..ec1808f3581 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsTest.java @@ -0,0 +1,172 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmTest.java new file mode 100644 index 00000000000..a0fc4196f67 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphAlgorithmTest.java @@ -0,0 +1,155 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphTestBase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphTestBase.java new file mode 100644 index 00000000000..61105e98f83 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/GoldbergMaximumDensitySubgraphTestBase.java @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.Assertions.assertEquals; + +/** + * base class for {@link GoldbergMaximumDensitySubgraphAlgorithm} testing + * + * @author Andre Immig + */ + +public abstract class GoldbergMaximumDensitySubgraphTestBase +{ + + protected static 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2DTest.java new file mode 100644 index 00000000000..54294ba7b5c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/CircularLayoutAlgorithm2DTest.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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))); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2DTest.java new file mode 100644 index 00000000000..6fe857bbe75 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2DTest.java @@ -0,0 +1,175 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRQuadTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRQuadTreeTest.java new file mode 100644 index 00000000000..436a1e1fcf3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRQuadTreeTest.java @@ -0,0 +1,89 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/GreedyTwoLayeredBipartiteLayout2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/GreedyTwoLayeredBipartiteLayout2DTest.java new file mode 100644 index 00000000000..49aace3a466 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/GreedyTwoLayeredBipartiteLayout2DTest.java @@ -0,0 +1,140 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2DTest.java new file mode 100644 index 00000000000..543f372f3cf --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/IndexedFRLayoutAlgorithm2DTest.java @@ -0,0 +1,193 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2DTest.java new file mode 100644 index 00000000000..1ae0f9f8e52 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RandomLayoutAlgorithm2DTest.java @@ -0,0 +1,64 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2DTest.java new file mode 100644 index 00000000000..4d588c89550 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/RescaleLayoutAlgorithm2DTest.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2DTest.java new file mode 100644 index 00000000000..8cd1008e824 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/TwoLayeredBipartiteLayout2DTest.java @@ -0,0 +1,156 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Box2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Box2DTest.java new file mode 100644 index 00000000000..2b7ecd90858 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Box2DTest.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/BoxesTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/BoxesTest.java new file mode 100644 index 00000000000..0a8d0e926a4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/BoxesTest.java @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2DTest.java new file mode 100644 index 00000000000..d299f8e68a5 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/ListenableLayoutModel2DTest.java @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/MapLayoutModel2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/MapLayoutModel2DTest.java new file mode 100644 index 00000000000..d79679c28e6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/MapLayoutModel2DTest.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Point2DTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Point2DTest.java new file mode 100644 index 00000000000..f4e00f12ffc --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Point2DTest.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/PointsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/PointsTest.java new file mode 100644 index 00000000000..1537929c4e1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/PointsTest.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImplTest.java new file mode 100644 index 00000000000..b1bc9a1dca3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImplTest.java @@ -0,0 +1,425 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/DinicMFImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/DinicMFImplTest.java new file mode 100644 index 00000000000..ea545796f63 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/DinicMFImplTest.java @@ -0,0 +1,127 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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); + } + + @BeforeEach + 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 + public void exceptionTest1() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMFImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMFImplTest.java new file mode 100644 index 00000000000..d4c1537a593 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMFImplTest.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2008-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +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); + assertThrows(NullPointerException.class, () -> new EdmondsKarpMFImpl(null)); + assertThrows(IllegalArgumentException.class, () -> new EdmondsKarpMFImpl<>(simple, -0.1)); + assertThrows(IllegalArgumentException.class, () -> { + simple.setEdgeWeight(e, -1.0); + new EdmondsKarpMFImpl<>(simple); + }); + assertThrows(UnsupportedOperationException.class, () -> { + simple.setEdgeWeight(e, 1.0); + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + Map flow = solver.getMaximumFlow(0, 1).getFlowMap(); + flow.put(e, 25.0); + }); + assertThrows(IllegalArgumentException.class, () -> { + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + solver.getMaximumFlow(2, 0); + }); + assertThrows(IllegalArgumentException.class, () -> { + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + solver.getMaximumFlow(1, 2); + }); + assertThrows(IllegalArgumentException.class, () -> { + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + solver.getMaximumFlow(0, 0); + }); + assertThrows(IllegalArgumentException.class, () -> { + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + solver.getMaximumFlow(null, 0); + }); + assertThrows(IllegalArgumentException.class, () -> { + MaximumFlowAlgorithm solver = + new EdmondsKarpMFImpl<>(simple); + solver.getMaximumFlow(0, null); + }); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMinimumSTCutTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMinimumSTCutTest.java new file mode 100644 index 00000000000..cde259b8694 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMinimumSTCutTest.java @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTreeTest.java new file mode 100644 index 00000000000..f93409e853e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldEquivalentFlowTreeTest.java @@ -0,0 +1,82 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTreeTest.java new file mode 100644 index 00000000000..09099b90184 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTreeTest.java @@ -0,0 +1,112 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldTreeAlgorithmsTestBase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldTreeAlgorithmsTestBase.java new file mode 100644 index 00000000000..33cc20eda41 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/GusfieldTreeAlgorithmsTestBase.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmTest.java new file mode 100644 index 00000000000..53e6ac95742 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmTest.java @@ -0,0 +1,281 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + + // 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowMinimumCutAlgorithmTestBase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowMinimumCutAlgorithmTestBase.java new file mode 100644 index 00000000000..833af386450 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowMinimumCutAlgorithmTestBase.java @@ -0,0 +1,1484 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.Assertions.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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MinimumSourceSinkCutTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MinimumSourceSinkCutTest.java new file mode 100644 index 00000000000..f3736cc7d60 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MinimumSourceSinkCutTest.java @@ -0,0 +1,270 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutsetTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutsetTest.java new file mode 100644 index 00000000000..4a518c576d9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PadbergRaoOddMinimumCutsetTest.java @@ -0,0 +1,231 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMFImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMFImplTest.java new file mode 100644 index 00000000000..c2e6bd92cef --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMFImplTest.java @@ -0,0 +1,95 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMinimumSTCutTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMinimumSTCutTest.java new file mode 100644 index 00000000000..e6afdc274be --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMinimumSTCutTest.java @@ -0,0 +1,128 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlowTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlowTest.java new file mode 100644 index 00000000000..21e61ae28fa --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/flow/mincost/CapacityScalingMinimumCostFlowTest.java @@ -0,0 +1,1510 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for {@link CapacityScalingMinimumCostFlow} + * + * @author Timofey Chudakov + */ +public class CapacityScalingMinimumCostFlowTest +{ + + private static final double EPS = 1e-9; + + public static int[] scalingFactors() { + return new int[] {1, 2, 3, 4, 5}; + } + + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow1(int scalingFactor) + { + int[][] testCase = new int[][] { { 1, 3 }, { 2, -3 }, { 1, 2, 0, 4, 5 } }; + test(testCase, scalingFactor, 15); + } + + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow2(int scalingFactor) + { + 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, scalingFactor, 26); + } + + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow3(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow4(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow5(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow6(int scalingFactor) + { + 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, scalingFactor, 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() + { + int 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow8(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow9(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow10(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow11(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow12(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow13(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow14(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow15(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow16(int scalingFactor) + { + 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, scalingFactor, 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 + */ + @ParameterizedTest + @MethodSource("scalingFactors") + public void testGetMinimumCostFlow17(int scalingFactor) + { + 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, scalingFactor, 4610258); + } + + private void test(int[][] testCase, int scalingFactor, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinderTest.java new file mode 100644 index 00000000000..e136b1b38c1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/independentset/ChordalGraphIndependentSetFinderTest.java @@ -0,0 +1,111 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + 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); + + 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))); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspectorTest.java new file mode 100644 index 00000000000..780b09fd56b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUForestIsomorphismInspectorTest.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link AHUForestIsomorphismInspector} + * + * @author Alexandru Valeanu + */ +public class AHUForestIsomorphismInspectorTest +{ + + @Test + public void testMissingSupplier() + { + assertThrows(UnsupportedOperationException.class, () -> { + 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 + public void testEmptyGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph tree1 = new SimpleGraph<>(DefaultEdge.class); + Set roots = new HashSet<>(); + + AHUForestIsomorphismInspector isomorphism = + new AHUForestIsomorphismInspector<>(tree1, roots, tree1, roots); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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")); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testNullGraphs() + { + assertThrows(NullPointerException.class, () -> new AHUForestIsomorphismInspector(null, new HashSet<>(), null, null)); + } + + @Test + public void testInvalidRoot() + { + assertThrows(IllegalArgumentException.class, () -> { + 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")); + + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + + assertTrue(areIsomorphic(forest1, forest2, treeMapping)); + } + + @Test + @Tag("slow") + 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))); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + @Tag("slow") + public void testRandomForests() + { + Random random = new Random(0x2312); + final int numTests = 1000; + + for (int test = 0; test < numTests; 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + } + + @Test + @Tag("slow") + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspectorTest.java new file mode 100644 index 00000000000..8ef4943750b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHURootedTreeIsomorphismInspectorTest.java @@ -0,0 +1,373 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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"); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testNullGraphs() + { + assertThrows(NullPointerException.class, () -> new AHURootedTreeIsomorphismInspector<>(null, null, null, null)); + } + + @Test + public void testOnlyOneNullGraph() + { + assertThrows(NullPointerException.class, () -> { + Graph tree1 = new SimpleGraph<>(DefaultEdge.class); + + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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"); + + assertFalse(isomorphism.isomorphismExists()); + 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"); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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)); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertFalse(isomorphism.isomorphismExists()); + assertNull(isomorphism.getMapping()); + + // Test as forest + + AHUForestIsomorphismInspector forestIsomorphism = + new AHUForestIsomorphismInspector<>(tree1, Set.of(1, 3), tree2, Set.of(11, 31)); + + assertTrue(forestIsomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = forestIsomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testInvalidRoot() + { + assertThrows(IllegalArgumentException.class, () -> { + 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)); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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)); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspectorTest.java new file mode 100644 index 00000000000..2540b8f43e2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/AHUUnrootedTreeIsomorphismInspectorTest.java @@ -0,0 +1,472 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link AHUUnrootedTreeIsomorphismInspector} + * + * @author Alexandru Valeanu + */ +public class AHUUnrootedTreeIsomorphismInspectorTest +{ + + @Test + public void testEmptyGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph tree1 = new SimpleGraph<>(DefaultEdge.class); + + AHUUnrootedTreeIsomorphismInspector isomorphism = + new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree1); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testNullGraphs() + { + assertThrows(NullPointerException.class, () -> new AHUUnrootedTreeIsomorphismInspector<>(null, null)); + } + + @Test + public void testOnlyOneNullGraph() + { + assertThrows(NullPointerException.class, () -> { + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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(); + + assertFalse(isomorphism.isomorphismExists()); + assertNull(isomorphism.getMapping()); + + // But they are isomorphic as unrooted trees + + AHUUnrootedTreeIsomorphismInspector isomorphism2 = + new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); + + assertTrue(isomorphism2.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism2.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + 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); + + assertFalse(isomorphism.isomorphismExists()); + assertNull(isomorphism.getMapping()); + + // Test as forest + + AHUForestIsomorphismInspector forestIsomorphism = + new AHUForestIsomorphismInspector<>(tree1, Set.of(1, 3), tree2, Set.of(11, 31)); + + assertTrue(forestIsomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = forestIsomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testInvalidRoot() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + @Tag("slow") + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + @Tag("slow") + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + @Tag("slow") + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + + isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); + + assertTrue(isomorphism.isomorphismExists()); + treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + + @Test + public void testRandomTrees() + { + Random random = new Random(0x88_88); + final int numTests = 1500; + + for (int test = 0; test < numTests; 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); + assertTrue(areIsomorphic(tree1, tree2, treeMapping)); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspectorTest.java new file mode 100644 index 00000000000..aa01e2bbccf --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/ColorRefinementIsomorphismInspectorTest.java @@ -0,0 +1,435 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + + assertThrows(IsomorphismUndecidableException.class, () -> isomorphismInspector.isomorphismExists()); + + 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(e); + } + assertFalse(isomorphismInspector.isColoringDiscrete()); + assertTrue(isomorphismInspector.isForest()); + + GraphMapping graphMapping = isomorphismInspector.getMappings().next(); + + assertEquals(4, graphMapping.getVertexCorrespondence(1, true)); + + 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)); + + 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), + graphMapping.getVertexCorrespondence(j, true)); + assertNotEquals( + graphMapping.getVertexCorrespondence(i, false), + graphMapping.getVertexCorrespondence(j, false)); + } + } + } + + @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(e); + } + assertTrue(isomorphismInspector.isColoringDiscrete()); + assertFalse(isomorphismInspector.isForest()); + + assertEquals(3, graphMapping.getVertexCorrespondence(1, true)); + assertEquals(5, graphMapping.getVertexCorrespondence(2, true)); + assertEquals(1, graphMapping.getVertexCorrespondence(3, true)); + assertEquals(2, graphMapping.getVertexCorrespondence(4, true)); + assertEquals(6, graphMapping.getVertexCorrespondence(5, true)); + assertEquals(4, graphMapping.getVertexCorrespondence(6, true)); + + assertEquals(3, graphMapping.getVertexCorrespondence(1, false)); + assertEquals(4, graphMapping.getVertexCorrespondence(2, false)); + assertEquals(1, graphMapping.getVertexCorrespondence(3, false)); + assertEquals(6, graphMapping.getVertexCorrespondence(4, false)); + assertEquals(2, graphMapping.getVertexCorrespondence(5, false)); + assertEquals(5, graphMapping.getVertexCorrespondence(6, false)); + } + + @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(e); + } + 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(e); + } + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/GraphOrderingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/GraphOrderingTest.java new file mode 100644 index 00000000000..0f1a37550cf --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/GraphOrderingTest.java @@ -0,0 +1,274 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * 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[] v1OutsExpected = { v2o, v3o, v4o }; + int[] v1Outs = g1Ordering.getOutEdges(v1o); + Arrays.sort(v1OutsExpected); + Arrays.sort(v1Outs); + + int[] v2OutsExpected = { v1o, v4o }; + int[] v2Outs = g1Ordering.getOutEdges(v2o); + Arrays.sort(v2OutsExpected); + Arrays.sort(v2Outs); + + int[] v3OutsExpected = { v1o }; + int[] v3Outs = g1Ordering.getOutEdges(v3o); + Arrays.sort(v3OutsExpected); + Arrays.sort(v3Outs); + + int[] v4OutsExpected = { v1o, v2o }; + int[] v4Outs = g1Ordering.getOutEdges(v4o); + Arrays.sort(v4OutsExpected); + Arrays.sort(v4Outs); + + int[] v5OutsExpected = {}; + int[] v5Outs = g1Ordering.getOutEdges(v5o); + Arrays.sort(v5OutsExpected); + Arrays.sort(v5Outs); + + assertArrayEquals(v1OutsExpected, v1Outs); + assertArrayEquals(v2OutsExpected, v2Outs); + assertArrayEquals(v3OutsExpected, v3Outs); + assertArrayEquals(v4OutsExpected, v4Outs); + assertArrayEquals(v5OutsExpected, v5Outs); + + int[] v1InsExpected = { v2o, v3o, v4o }; + int[] v1Ins = g1Ordering.getOutEdges(v1o); + Arrays.sort(v1InsExpected); + Arrays.sort(v1Ins); + + int[] v2InsExpected = { v1o, v4o }; + int[] v2Ins = g1Ordering.getOutEdges(v2o); + Arrays.sort(v2InsExpected); + Arrays.sort(v2Ins); + + int[] v3InsExpected = { v1o }; + int[] v3Ins = g1Ordering.getOutEdges(v3o); + Arrays.sort(v3InsExpected); + Arrays.sort(v3Ins); + + int[] v4InsExpected = { v1o, v2o }; + int[] v4Ins = g1Ordering.getOutEdges(v4o); + Arrays.sort(v4InsExpected); + Arrays.sort(v4Ins); + + int[] v5InsExpected = {}; + int[] v5Ins = g1Ordering.getOutEdges(v5o); + Arrays.sort(v5InsExpected); + Arrays.sort(v5Ins); + + assertArrayEquals(v1InsExpected, v1Ins); + assertArrayEquals(v2InsExpected, v2Ins); + assertArrayEquals(v3InsExpected, v3Ins); + assertArrayEquals(v4InsExpected, v4Ins); + assertArrayEquals(v5InsExpected, v5Ins); + + assertFalse(g1Ordering.hasEdge(v1o, v1o)); + assertTrue(g1Ordering.hasEdge(v1o, v2o)); + assertTrue(g1Ordering.hasEdge(v1o, v3o)); + assertTrue(g1Ordering.hasEdge(v1o, v4o)); + assertFalse(g1Ordering.hasEdge(v1o, v5o)); + assertTrue(g1Ordering.hasEdge(v2o, v1o)); + assertFalse(g1Ordering.hasEdge(v2o, v2o)); + assertFalse(g1Ordering.hasEdge(v2o, v3o)); + assertTrue(g1Ordering.hasEdge(v2o, v4o)); + assertFalse(g1Ordering.hasEdge(v2o, v5o)); + assertTrue(g1Ordering.hasEdge(v3o, v1o)); + assertFalse(g1Ordering.hasEdge(v3o, v2o)); + assertFalse(g1Ordering.hasEdge(v3o, v3o)); + assertFalse(g1Ordering.hasEdge(v3o, v4o)); + assertFalse(g1Ordering.hasEdge(v3o, v5o)); + assertTrue(g1Ordering.hasEdge(v4o, v1o)); + assertTrue(g1Ordering.hasEdge(v4o, v2o)); + assertFalse(g1Ordering.hasEdge(v4o, v3o)); + assertFalse(g1Ordering.hasEdge(v4o, v4o)); + assertFalse(g1Ordering.hasEdge(v4o, v5o)); + assertFalse(g1Ordering.hasEdge(v5o, v1o)); + assertFalse(g1Ordering.hasEdge(v5o, v2o)); + assertFalse(g1Ordering.hasEdge(v5o, v3o)); + assertFalse(g1Ordering.hasEdge(v5o, v4o)); + assertFalse(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[] v1OutsExpected = { v2o }; + int[] v1Outs = g1Ordering.getOutEdges(v1o); + Arrays.sort(v1OutsExpected); + Arrays.sort(v1Outs); + + int[] v2OutsExpected = { v3o }; + int[] v2Outs = g1Ordering.getOutEdges(v2o); + Arrays.sort(v2OutsExpected); + Arrays.sort(v2Outs); + + int[] v3OutsExpected = { v2o, v4o }; + int[] v3Outs = g1Ordering.getOutEdges(v3o); + Arrays.sort(v3OutsExpected); + Arrays.sort(v3Outs); + + int[] v4OutsExpected = {}; + int[] v4Outs = g1Ordering.getOutEdges(v4o); + Arrays.sort(v4OutsExpected); + Arrays.sort(v4Outs); + + int[] v5OutsExpected = {}; + int[] v5Outs = g1Ordering.getOutEdges(v5o); + Arrays.sort(v5OutsExpected); + Arrays.sort(v5Outs); + + assertArrayEquals(v1OutsExpected, v1Outs); + assertArrayEquals(v2OutsExpected, v2Outs); + assertArrayEquals(v3OutsExpected, v3Outs); + assertArrayEquals(v4OutsExpected, v4Outs); + assertArrayEquals(v5OutsExpected, v5Outs); + + int[] v1InsExpected = {}; + int[] v1Ins = g1Ordering.getInEdges(v1o); + Arrays.sort(v1InsExpected); + Arrays.sort(v1Ins); + + int[] v2InsExpected = { v1o, v3o }; + int[] v2Ins = g1Ordering.getInEdges(v2o); + Arrays.sort(v2InsExpected); + Arrays.sort(v2Ins); + + int[] v3InsExpected = { v2o }; + int[] v3Ins = g1Ordering.getInEdges(v3o); + Arrays.sort(v3InsExpected); + Arrays.sort(v3Ins); + + int[] v4InsExpected = { v3o }; + int[] v4Ins = g1Ordering.getInEdges(v4o); + Arrays.sort(v4InsExpected); + Arrays.sort(v4Ins); + + int[] v5InsExpected = {}; + int[] v5Ins = g1Ordering.getInEdges(v5o); + Arrays.sort(v5InsExpected); + Arrays.sort(v5Ins); + + assertArrayEquals(v1InsExpected, v1Ins); + assertArrayEquals(v2InsExpected, v2Ins); + assertArrayEquals(v3InsExpected, v3Ins); + assertArrayEquals(v4InsExpected, v4Ins); + assertArrayEquals(v5InsExpected, v5Ins); + + assertFalse(g1Ordering.hasEdge(v1o, v1o)); + assertTrue(g1Ordering.hasEdge(v1o, v2o)); + assertFalse(g1Ordering.hasEdge(v1o, v3o)); + assertFalse(g1Ordering.hasEdge(v1o, v4o)); + assertFalse(g1Ordering.hasEdge(v1o, v5o)); + assertFalse(g1Ordering.hasEdge(v2o, v1o)); + assertFalse(g1Ordering.hasEdge(v2o, v2o)); + assertTrue(g1Ordering.hasEdge(v2o, v3o)); + assertFalse(g1Ordering.hasEdge(v2o, v4o)); + assertFalse(g1Ordering.hasEdge(v2o, v5o)); + assertFalse(g1Ordering.hasEdge(v3o, v1o)); + assertTrue(g1Ordering.hasEdge(v3o, v2o)); + assertFalse(g1Ordering.hasEdge(v3o, v3o)); + assertTrue(g1Ordering.hasEdge(v3o, v4o)); + assertFalse(g1Ordering.hasEdge(v3o, v5o)); + assertFalse(g1Ordering.hasEdge(v4o, v1o)); + assertFalse(g1Ordering.hasEdge(v4o, v2o)); + assertFalse(g1Ordering.hasEdge(v4o, v3o)); + assertFalse(g1Ordering.hasEdge(v4o, v4o)); + assertFalse(g1Ordering.hasEdge(v4o, v5o)); + assertFalse(g1Ordering.hasEdge(v5o, v1o)); + assertFalse(g1Ordering.hasEdge(v5o, v2o)); + assertFalse(g1Ordering.hasEdge(v5o, v3o)); + assertFalse(g1Ordering.hasEdge(v5o, v4o)); + assertFalse(g1Ordering.hasEdge(v5o, v5o)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMappingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMappingTest.java new file mode 100644 index 00000000000..6176ae85931 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphicGraphMappingTest.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + + assertTrue(isomorphism.isomorphismExists()); + 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); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping mapping12 = isomorphism.getMapping(); + + isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree2, tree3); + + assertTrue(isomorphism.isomorphismExists()); + IsomorphicGraphMapping mapping23 = isomorphism.getMapping(); + + IsomorphicGraphMapping mapping13 = mapping12.compose(mapping23); + + assertTrue(areIsomorphic(tree1, tree3, mapping13)); + } + + @Test + public void testCompositionOfRandomMappings() + { + final int numTests = 1000; + Random random = new Random(0x11_88_11); + + for (int test = 0; test < numTests; 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); + + assertTrue(areIsomorphic(tree1, tree3, mapping13)); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphismTestUtil.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphismTestUtil.java new file mode 100644 index 00000000000..d01845dbf92 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/IsomorphismTestUtil.java @@ -0,0 +1,147 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/SubgraphIsomorphismTestUtils.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/SubgraphIsomorphismTestUtils.java new file mode 100644 index 00000000000..46e0c9d83ce --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/SubgraphIsomorphismTestUtils.java @@ -0,0 +1,281 @@ +/* + * (C) Copyright 2015-2023, 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 final 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspectorTest.java new file mode 100644 index 00000000000..116de8e8143 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2GraphIsomorphismInspectorTest.java @@ -0,0 +1,190 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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]")); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertFalse(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); + assertFalse(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); + + assertFalse(vf2.isomorphismExists()); + assertFalse(vf3.isomorphismExists()); + assertFalse(vf4.isomorphismExists()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspectorTest.java new file mode 100644 index 00000000000..60595ff746b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/VF2SubgraphIsomorphismInspectorTest.java @@ -0,0 +1,890 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +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 */ + assertThrows(NullPointerException.class, () -> new VF2SubgraphIsomorphismInspector<>(null, sg1)); + + /* GT-1: multigraphs */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(mg1, mg1)); + + /* GT-2: pseudographs */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(pg1, pg1)); + + /* GT-3: simple graphs */ + VF2SubgraphIsomorphismInspector gt3 = + new VF2SubgraphIsomorphismInspector<>(sg1, sg1); + assertTrue(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 */ + + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(sg1, mg1)); + + /* GT-6: simple graph + pseudograph */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(sg1, pg1)); + + /* GT-7: directed graph + multigraph */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(dg1, mg1)); + + /* GT-8: directed graph + pseudograph */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(dg1, pg1)); + + /* GT-9: pseudograph + multigraph */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(pg1, mg1)); + + /* GT-10: simple graph + directed graph */ + assertThrows(IllegalArgumentException.class, () -> new VF2SubgraphIsomorphismInspector<>(sg1, dg1)); + } + + /** + * 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); + + assertFalse(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]")); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertTrue(mappings.remove(iter.next().toString())); + assertFalse(iter.hasNext()); + + /* ECS-5: graph empty, subgraph single vertex */ + + VF2SubgraphIsomorphismInspector vfs5 = + new VF2SubgraphIsomorphismInspector<>(sg0v, sg1v); + + assertFalse(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); + + assertFalse(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]")); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertFalse(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); + + assertFalse(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); + + assertFalse(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())); + } + assertFalse(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++) { + assertTrue(mappings12.remove(iter12.next().toString())); + } + assertFalse(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); + + assertFalse(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]")); + assertTrue(mappings.remove(iter4.next().toString())); + assertTrue(mappings.remove(iter4.next().toString())); + assertTrue(mappings.remove(iter4.next().toString())); + assertTrue(mappings.remove(iter4.next().toString())); + assertFalse(iter4.hasNext()); + + /* ECD-5: graph empty, subgraph single vertex */ + + VF2SubgraphIsomorphismInspector vf5 = + new VF2SubgraphIsomorphismInspector<>(dg0v, dg1v); + + assertFalse(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); + + assertFalse(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]")); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertTrue(mappings7.remove(iter7.next().toString())); + assertFalse(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); + + assertFalse(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); + + assertFalse(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]")); + assertTrue(mappings10.remove(iter10.next().toString())); + assertTrue(mappings10.remove(iter10.next().toString())); + assertTrue(mappings10.remove(iter10.next().toString())); + assertTrue(mappings10.remove(iter10.next().toString())); + assertTrue(mappings10.remove(iter10.next().toString())); + assertFalse(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()); + assertFalse(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]")); + assertTrue(mappings12.remove(iter12.next().toString())); + assertTrue(mappings12.remove(iter12.next().toString())); + assertTrue(mappings12.remove(iter12.next().toString())); + assertFalse(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); + + assertTrue(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); + + assertTrue(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); + + assertTrue(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"); + + assertTrue(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()); + assertFalse(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); + + assertTrue(vf2.isomorphismExists()); + + SubgraphIsomorphismTestUtils.showLog( + "|V1| = " + g1.vertexSet().size() + ", |E1| = " + g1.edgeSet().size() + ", |V2| = " + + g2.vertexSet().size() + ", |E2| = " + g2.edgeSet().size() + " - " + + (System.currentTimeMillis() - time) + "ms"); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinderTest.java new file mode 100644 index 00000000000..125b079ec65 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinderTest.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinderTest.java new file mode 100644 index 00000000000..2d4b61f738d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinderTest.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/HeavyPathLCAFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/HeavyPathLCAFinderTest.java new file mode 100644 index 00000000000..81feaa85a39 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/HeavyPathLCAFinderTest.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/LCATreeTestBase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/LCATreeTestBase.java new file mode 100644 index 00000000000..807011041e4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/LCATreeTestBase.java @@ -0,0 +1,594 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for LCA algorithms on rooted trees and forests + * + * @author Alexandru Valeanu + */ +public abstract class LCATreeTestBase +{ + + abstract LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots); + + @Test + public void testInvalidNode() + { + assertThrows(IllegalArgumentException.class, () -> { + 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"); + + assertNull(createSolver(g, Collections.singleton("a")).getLCA("a", "d")); + } + + @Test + public void testOneNode() + { + Graph g = new SimpleGraph<>(DefaultEdge.class); + g.addVertex("a"); + + assertEquals("a", createSolver(g, Collections.singleton("a")).getLCA("a", "a")); + } + + @Test + public void testTwoRootsInTheSameTree() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testTwoRootsInTheSameTree2() + { + assertThrows(IllegalArgumentException.class, () -> { + 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")); + + assertNull(lcaAlgorithm.getLCA("a", "b")); + assertNull(lcaAlgorithm.getLCA("b", "a")); + assertEquals("a", lcaAlgorithm.getLCA("a", "a")); + 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")); + + assertNull(lcaAlgorithm.getLCA("b", "c")); + assertNull(lcaAlgorithm.getLCA("c", "b")); + assertNull(lcaAlgorithm.getLCA("c", "a")); + assertNull(lcaAlgorithm.getLCA("a", "c")); + assertNull(lcaAlgorithm.getLCA("a", "b")); + assertNull(lcaAlgorithm.getLCA("b", "a")); + assertEquals("a", lcaAlgorithm.getLCA("a", "a")); + assertEquals("b", lcaAlgorithm.getLCA("b", "b")); + } + + @Test + public void testEmptyGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph g = new SimpleGraph<>(DefaultEdge.class); + + createSolver(g, Collections.singleton("a")); + }); + } + + @Test + public void testEmptySetOfRoots() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph g = new SimpleGraph<>(DefaultEdge.class); + g.addVertex("a"); + + createSolver(g, Collections.emptySet()); + }); + } + + @Test + public void testRootNotInGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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++) { + 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(); + + 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")); + + assertEquals("b", lcaAlgorithm.getLCA("c", "e")); + assertEquals("b", lcaAlgorithm.getLCA("b", "d")); + 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)); + + assertEquals(3, (int) lcaAlgorithm.getLCA(10, 11)); + assertEquals(2, (int) lcaAlgorithm.getLCA(8, 9)); + assertEquals(1, (int) lcaAlgorithm.getLCA(5, 11)); + assertEquals(2, (int) lcaAlgorithm.getLCA(5, 6)); + assertEquals(2, (int) lcaAlgorithm.getLCA(4, 2)); + assertEquals(2, (int) lcaAlgorithm.getLCA(4, 5)); + assertEquals(2, (int) lcaAlgorithm.getLCA(2, 2)); + assertEquals(2, (int) lcaAlgorithm.getLCA(8, 6)); + 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)); + + assertEquals(1, (int) lcaAlgorithm.getLCA(9, 14)); + assertEquals(1, (int) lcaAlgorithm.getLCA(10, 9)); + assertEquals(15, (int) lcaAlgorithm.getLCA(15, 15)); + assertEquals(1, (int) lcaAlgorithm.getLCA(1, 17)); + assertEquals(3, (int) lcaAlgorithm.getLCA(3, 3)); + assertEquals(1, (int) lcaAlgorithm.getLCA(3, 1)); + assertEquals(1, (int) lcaAlgorithm.getLCA(11, 14)); + assertEquals(6, (int) lcaAlgorithm.getLCA(18, 19)); + assertEquals(2, (int) lcaAlgorithm.getLCA(12, 2)); + 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")); + + assertEquals("b", lcaAlgorithm.getLCA("b", "h")); + assertEquals("b", lcaAlgorithm.getLCA("j", "f")); + 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); + + assertEquals(Arrays.asList("b", "b", "c"), lcas); + + // test it the other way around and starting from b + 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++) { + 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 numTrees = 100 + random.nextInt(200); + + Graph g = new SimpleGraph<>( + SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); + + BarabasiAlbertForestGenerator generator = + new BarabasiAlbertForestGenerator<>(numTrees, 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++) { + 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++) { + 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++) { + assertEquals(lcas1.get(i), lcas2.get(i)); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/NaiveLCAFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/NaiveLCAFinderTest.java new file mode 100644 index 00000000000..2ef242b1f29 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/NaiveLCAFinderTest.java @@ -0,0 +1,240 @@ +/* + * (C) Copyright 2016-2023, 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.junit.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * 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); + assertTrue(lcaSet.containsAll(expectedSet)); + assertEquals(expectedSet.size(), lcaSet.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); + + assertEquals("f", finder.getLCA("f", "h")); + assertEquals("f", finder.getLCA("h", "f")); + assertEquals("b", finder.getLCA("g", "h")); + assertEquals("c", finder.getLCA("c", "c")); + 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); + + assertNull(finder.getLCA("i", "e")); + 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); + + assertEquals("f", finder.getLCA("h", "f")); + assertNull(finder.getLCA("a", "i")); + + checkLcas(finder, "h", "f", Arrays.asList("f")); + 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); + + assertEquals("b", finder.getLCA("b", "h")); + 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")); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/lca/TarjanLCAFinderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/TarjanLCAFinderTest.java new file mode 100644 index 00000000000..7dd3d09370d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/lca/TarjanLCAFinderTest.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPredictionTest.java new file mode 100644 index 00000000000..2cf0279b4de --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/AdamicAdarIndexLinkPredictionTest.java @@ -0,0 +1,132 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPredictionTest.java new file mode 100644 index 00000000000..d38126f5a48 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/CommonNeighborsLinkPredictionTest.java @@ -0,0 +1,57 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPredictionTest.java new file mode 100644 index 00000000000..620ccd570e3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubDepressedIndexLinkPredictionTest.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPredictionTest.java new file mode 100644 index 00000000000..181d2fcec05 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/HubPromotedIndexLinkPredictionTest.java @@ -0,0 +1,70 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPredictionTest.java new file mode 100644 index 00000000000..130ec220590 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/JaccardCoefficientLinkPredictionTest.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPredictionTest.java new file mode 100644 index 00000000000..75d2454973a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/LeichtHolmeNewmanIndexLinkPredictionTest.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentIndexLinkPredictionTest.java new file mode 100644 index 00000000000..90d13645f78 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/PreferentialAttachmentIndexLinkPredictionTest.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPredictionTest.java new file mode 100644 index 00000000000..da3259d939b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/ResourceAllocationIndexLinkPredictionTest.java @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPredictionTest.java new file mode 100644 index 00000000000..f937886cf11 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SaltonIndexLinkPredictionTest.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPredictionTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPredictionTest.java new file mode 100644 index 00000000000..d50f279d008 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/SorensenIndexLinkPredictionTest.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.Test; + +/** + * Tests for class SorensenIndexLinkPrediction + * + * @author Dimitrios Michail + */ +public class SorensenIndexLinkPredictionTest +{ + + @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 } }); + + SorensenIndexLinkPrediction alg = + new SorensenIndexLinkPrediction<>(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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + Graph g = GraphTypeBuilder + .directed().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) + .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); + + g.addVertex(0); + g.addVertex(1); + + SorensenIndexLinkPrediction alg = + new SorensenIndexLinkPrediction<>(g); + + alg.predict(0, 1); + }); + } + +} diff --git "a/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPredictionTest.java" "b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPredictionTest.java" new file mode 100644 index 00000000000..19e4ccc7309 --- /dev/null +++ "b/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/S\303\270rensenIndexLinkPredictionTest.java" @@ -0,0 +1,93 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.Test; + +/** + * Tests for class SørensenIndexLinkPrediction + * + * @author Dimitrios Michail + * + * @deprecated Class will be replaced by SorensenIndexLinkPredictionTest + */ +@Deprecated +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 + public void testInvalidPrediction() + { + assertThrows(LinkPredictionIndexNotWellDefinedException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/ApproximateWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/ApproximateWeightedMatchingTest.java new file mode 100644 index 00000000000..98186af2a1f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/ApproximateWeightedMatchingTest.java @@ -0,0 +1,417 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/BasePathGrowingWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/BasePathGrowingWeightedMatchingTest.java new file mode 100644 index 00000000000..47bf83f1558 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/BasePathGrowingWeightedMatchingTest.java @@ -0,0 +1,153 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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()); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatchingTest.java new file mode 100644 index 00000000000..61f2f96c33c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/DenseEdmondsMaximumCardinalityMatchingTest.java @@ -0,0 +1,1178 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatchingTest.java new file mode 100644 index 00000000000..6ea10ca0972 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyMaximumCardinalityMatchingTest.java @@ -0,0 +1,110 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyWeightedMatchingTest.java new file mode 100644 index 00000000000..1f31a400c23 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/GreedyWeightedMatchingTest.java @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + }; + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatchingTest.java new file mode 100644 index 00000000000..e8b83955c1b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/HopcroftKarpMaximumCardinalityBipartiteMatchingTest.java @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2012-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatchingTest.java new file mode 100644 index 00000000000..9b1d3599c6f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/KuhnMunkresMinimalWeightBipartitePerfectMatchingTest.java @@ -0,0 +1,419 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class KuhnMunkresMinimalWeightBipartitePerfectMatchingTest +{ + + interface V + { + } + + /** + * First partition + */ + private enum FirstPartition + 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(FirstPartition.values()); + + /** + * Second partition + */ + private enum SecondPartition + 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(SecondPartition.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); + + 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(); + + assertEquals(12, w); + } + + @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(); + + assertEquals(15, w); + + } + + @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(); + + assertEquals(10, w); + + } + + @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(); + + assertEquals(12, w); + + } + + @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(); + + assertEquals(21, w); + } + + @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(); + + assertEquals(19, w); + } + + @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(); + + assertEquals(12, w); + } + + @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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumCardinalityBipartiteMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumCardinalityBipartiteMatchingTest.java new file mode 100644 index 00000000000..80643b51948 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumCardinalityBipartiteMatchingTest.java @@ -0,0 +1,249 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatchingTest.java new file mode 100644 index 00000000000..9e9fb34f70e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/MaximumWeightBipartiteMatchingTest.java @@ -0,0 +1,257 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.math.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MaximumWeightBipartiteMatchingTest +{ + + private SimpleWeightedGraph graph; + private Set partition1; + private Set partition2; + + private MaximumWeightBipartiteMatching matcher; + + @BeforeEach + 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 + @Tag("slow") + public void testRandomInstancesFixedSeed() + { + testRandomInstance(new Random(17), 100, 0.7, 2); + } + + @Test + @Tag("slow") + 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); + } + } + + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/NoHeuristicsPathGrowingWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/NoHeuristicsPathGrowingWeightedMatchingTest.java new file mode 100644 index 00000000000..9770134ea57 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/NoHeuristicsPathGrowingWeightedMatchingTest.java @@ -0,0 +1,220 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/PathGrowingWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/PathGrowingWeightedMatchingTest.java new file mode 100644 index 00000000000..6e66c6b82b8 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/PathGrowingWeightedMatchingTest.java @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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); + }; + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatchingTest.java new file mode 100644 index 00000000000..99ca32a651b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/SparseEdmondsMaximumCardinalityMatchingTest.java @@ -0,0 +1,1268 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDebugger.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDebugger.java new file mode 100644 index 00000000000..de784a05f61 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDebugger.java @@ -0,0 +1,246 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdaterTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdaterTest.java new file mode 100644 index 00000000000..4995f96aad1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVDualUpdaterTest.java @@ -0,0 +1,352 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link BlossomVDualUpdater} + * + * @author Timofey Chudakov + */ +@SuppressWarnings("unused") +public class BlossomVDualUpdaterTest +{ + + private BlossomVOptions noneOptions = new BlossomVOptions(NONE); + + @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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializerTest.java new file mode 100644 index 00000000000..f2bd548b43e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVInitializerTest.java @@ -0,0 +1,387 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link BlossomVInitializer} + * + * @author Timofey Chudakov + */ +public class BlossomVInitializerTest +{ + + private BlossomVOptions fractionalOptions = new BlossomVOptions(FRACTIONAL); + + /** + * Tests greedy initialization + */ + @Test + @SuppressWarnings("unused") + 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 + @SuppressWarnings("unused") + 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); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNodeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNodeTest.java new file mode 100644 index 00000000000..87fdbe61369 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVNodeTest.java @@ -0,0 +1,477 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link BlossomVNode} + * + * @author Timofey Chudakov + */ +@SuppressWarnings("unused") +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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdaterTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdaterTest.java new file mode 100644 index 00000000000..269dc63b29e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVPrimalUpdaterTest.java @@ -0,0 +1,2006 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link BlossomVPrimalUpdater} + * + * @author Timofey Chudakov + */ +@SuppressWarnings("unused") +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); + assertEquals(BlossomVNode.Label.INFINITY, node1.label); + 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); + + assertEquals(BlossomVNode.Label.INFINITY, node2.label); + 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); + + assertEquals(BlossomVNode.Label.INFINITY, node4.label); + 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); + + assertEquals(BlossomVNode.Label.INFINITY, node1.label); + assertEquals(BlossomVNode.Label.INFINITY, node2.label); + assertEquals(BlossomVNode.Label.INFINITY, node3.label); + assertEquals(BlossomVNode.Label.INFINITY, node4.label); + assertEquals(BlossomVNode.Label.INFINITY, node5.label); + 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); + + assertEquals(BlossomVNode.Label.INFINITY, node1.label); + assertEquals(BlossomVNode.Label.INFINITY, node2.label); + assertEquals(BlossomVNode.Label.INFINITY, node3.label); + assertEquals(BlossomVNode.Label.INFINITY, node4.label); + assertEquals(BlossomVNode.Label.INFINITY, node5.label); + assertEquals(BlossomVNode.Label.INFINITY, node6.label); + assertEquals(BlossomVNode.Label.INFINITY, node7.label); + assertEquals(BlossomVNode.Label.INFINITY, node8.label); + assertEquals(BlossomVNode.Label.INFINITY, node9.label); + 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); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVStateTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVStateTest.java new file mode 100644 index 00000000000..51816f0f767 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVStateTest.java @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdgeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdgeTest.java new file mode 100644 index 00000000000..2efe7301dee --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeEdgeTest.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeTest.java new file mode 100644 index 00000000000..7235dffc43d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/BlossomVTreeTest.java @@ -0,0 +1,147 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link BlossomVTree} + * + * @author Timofey Chudakov + */ +public class BlossomVTreeTest +{ + + private BlossomVOptions noneOptions = new BlossomVOptions(NONE); + + @Test + @SuppressWarnings("unused") + 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatchingTest.java new file mode 100644 index 00000000000..e63fd1cb716 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedMatchingTest.java @@ -0,0 +1,1636 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 + */ + +public class KolmogorovWeightedMatchingTest +{ + + /** + * Generate all combinations of options and algorithm objective sense. + * + * @return all combinations of options and algorithm objective sense. + */ + 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; + } + + @ParameterizedTest + @MethodSource("params") + public void testGetMatching1(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 0, 1, -1 }, { 1, 2, 1 }, { 2, 3, -1 }, { 3, 0, 1 }, }; + double maxWeight = 2; + double minWeight = -2; + test(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + @ParameterizedTest + @MethodSource("params") + public void testGetMatching2(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Triangulation of 3 points with randomly negated edge weights + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching3(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 2, 0, -2 }, { 1, 2, 2 }, { 0, 1, 3 }, }; + double maxWeight = 3; + double minWeight = -2; + test(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Triangulation of 10 points with randomly negated edge weights + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching4(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Triangulation of 10 points with randomly negated edge weights + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching5(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Triangulation of 50 points with randomly negated edge weights + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching6(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Triangulation of 100 points with randomly negated edge weights + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching7(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 4 vertices and 4 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching8(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 0, 1, 7 }, { 3, 2, 9 }, { 3, 0, -8 }, { 1, 2, 7 }, }; + double maxWeight = 16; + double minWeight = -8; + test(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 10 vertices and 20 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching9(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 15 vertices and 40 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching10(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 30 vertices and 100 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching11(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 50 vertices and 100 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching12(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 100 vertices and 100 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching13(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 100 vertices and 200 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching14(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 100 vertices and 400 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching15(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 200 vertices and 500 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching16(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 400 vertices and 1000 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching17(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 500 vertices and 1500 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching18(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Random graph with 1500 vertices and 1500 edges + */ + @ParameterizedTest + @MethodSource("params") + public void testGetMatching19(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + private void test(BlossomVOptions options, 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatchingTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatchingTest.java new file mode 100644 index 00000000000..3fd37413af0 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/KolmogorovWeightedPerfectMatchingTest.java @@ -0,0 +1,2565 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +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.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link KolmogorovWeightedPerfectMatching} + * + * @author Timofey Chudakov + */ +public class KolmogorovWeightedPerfectMatchingTest +{ + + /** + * 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); + } + } + } + + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testInvalidDualSolution(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching0(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on an empty graph + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching1(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching2(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 1, 2, 5 } }; + double maxWeight = 5; + double minWeight = 5; + test(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a graph with an odd cycle + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching3(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{2,2}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching4(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 1, 3, 11 }, { 1, 4, 8 }, { 2, 3, 8 }, { 2, 4, 2 } }; + double maxWeight = 16; + double minWeight = 13; + test(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{3,3}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching5(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{3,3}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching6(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{4}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching7(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (2,5) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching8(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (0,5) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching9(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (0,5) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching10(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (0,5) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching11(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (0,5) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching12(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (4,3) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching13(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (4,0) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching14(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{5}$ with a dummy edge (4,0) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching15(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a $K_{6}$ with edges with zero weight + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching16(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a triangulation of 8 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching17(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a triangulation of 10 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching18(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching19(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching20(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching21(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching22(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching23(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching24(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching25(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a triangulation of 10 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching26(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching27(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, 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) + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching28(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 50 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching29(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 50 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching30(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 100 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching31(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 100 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching32(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 200 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching33(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 300 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching34(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 400 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching35(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on triangulation of 500 points + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching36(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on an unweighted $K_{50}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching37(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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}$ + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching38(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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(options, edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a small pseudograph + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching39(BlossomVOptions options, ObjectiveSense objectiveSense) + { + int[][] edges = new int[][] { { 1, 1, 1 }, { 1, 2, 5 }, { 1, 2, 10 }, }; + double maxWeight = 10; + double minWeight = 5; + test( + options, new WeightedPseudograph<>(DefaultEdge.class), edges, + objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a pseudograph + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching40(BlossomVOptions options, ObjectiveSense objectiveSense) + { + 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( + options, new WeightedPseudograph<>(DefaultEdge.class), edges, + objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); + } + + /** + * Test on a graph with odd number of vertices + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching41(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(1); + + KolmogorovWeightedPerfectMatching matching = + new KolmogorovWeightedPerfectMatching<>(graph, options); + matching.getMatching(); + }); + } + + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching42(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching43(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching44(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching45(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching46(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching47(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching48(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching49(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + */ + @ParameterizedTest + @MethodSource("org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedMatchingTest#params") + public void testGetMatching50(BlossomVOptions options, ObjectiveSense objectiveSense) + { + assertThrows(IllegalArgumentException.class, () -> { + 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(BlossomVOptions options, int[][] edges, double result, ObjectiveSense objectiveSense) + { + test( + options, 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( + BlossomVOptions options, + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/partition/BipartitePartitioningTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/partition/BipartitePartitioningTest.java new file mode 100644 index 00000000000..44f2cc72265 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/partition/BipartitePartitioningTest.java @@ -0,0 +1,293 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.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); + + assertTrue(finder.isBipartite()); + 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); + + assertTrue(finder.isBipartite()); + 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); + + assertTrue(finder.isBipartite()); + 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); + + assertTrue(finder.isBipartite()); + 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); + + assertTrue(finder.isBipartite()); + 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); + + assertFalse(finder.isBipartite()); + 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); + + assertTrue(finder.isBipartite()); + 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); + + assertFalse(finder.isBipartite()); + assertNull(finder.getPartitioning()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspectorTest.java new file mode 100644 index 00000000000..84428a88fdd --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/planar/BoyerMyrvoldPlanarityInspectorTest.java @@ -0,0 +1,938 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testNonPlanarGraphNoEmbedding() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testPlanarGraphNoKuratowskiSubdivision() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ApBetweennessCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ApBetweennessCentralityTest.java new file mode 100644 index 00000000000..5443b2a8a80 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ApBetweennessCentralityTest.java @@ -0,0 +1,52 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.apfloat.Apfloat; +import org.jgrapht.Graph; +import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.SimpleDirectedGraph; +import org.junit.jupiter.api.Test; + +public class ApBetweennessCentralityTest +{ + + @Test + public void testNoOverflow() + { + 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 ApBetweennessCentrality<>(g); + Map scores = bc.getScores(); + + assertEquals(scores.get(9), new Apfloat(0)); + assertEquals(scores.get(10), new Apfloat("3.28e3")); + assertEquals(scores.get(3289), new Apfloat("3.28e3")); + assertEquals(scores.get(3290), new Apfloat("0")); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/BetweennessCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/BetweennessCentralityTest.java new file mode 100644 index 00000000000..f47c1b782c9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/BetweennessCentralityTest.java @@ -0,0 +1,588 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BetweennessCentralityTest +{ + + @Test + public void testNullGraph() + { + assertThrows(NullPointerException.class, () -> { + 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 + public void testOverflow() + { + assertThrows(ArithmeticException.class, () -> { + 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 + @Tag("slow") + 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClosenessCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClosenessCentralityTest.java new file mode 100644 index 00000000000..57863d44105 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClosenessCentralityTest.java @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClusteringCoefficientTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClusteringCoefficientTest.java new file mode 100644 index 00000000000..331e9cead8a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/ClusteringCoefficientTest.java @@ -0,0 +1,384 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testNullGraphClusteringCoefficient() + { + assertThrows(NullPointerException.class, () -> 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/CorenessTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/CorenessTest.java new file mode 100644 index 00000000000..9e88411ed29 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/CorenessTest.java @@ -0,0 +1,145 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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(0, pr.getVertexScore("a")); + assertEquals(1, pr.getVertexScore("b")); + assertEquals(1, pr.getVertexScore("c")); + assertEquals(1, pr.getVertexScore("d")); + assertEquals(3, pr.getVertexScore("e")); + assertEquals(3, pr.getVertexScore("f")); + assertEquals(3, pr.getVertexScore("g")); + assertEquals(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(0, pr.getVertexScore("a")); + assertEquals(1, pr.getVertexScore("b")); + assertEquals(1, pr.getVertexScore("c")); + assertEquals(1, pr.getVertexScore("d")); + assertEquals(3, pr.getVertexScore("e")); + assertEquals(3, pr.getVertexScore("f")); + assertEquals(3, pr.getVertexScore("g")); + assertEquals(3, pr.getVertexScore("h")); + assertEquals(2, pr.getVertexScore("i")); + assertEquals(2, pr.getVertexScore("j")); + assertEquals(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(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); + + assertThrows(IllegalArgumentException.class, () -> pr.getVertexScore("unknown")); + } + + @Test + public void testBadParameters() + { + assertThrows(NullPointerException.class, () -> new Coreness<>(null)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EdgeBetweennessCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EdgeBetweennessCentralityTest.java new file mode 100644 index 00000000000..c15a640950f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EdgeBetweennessCentralityTest.java @@ -0,0 +1,239 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.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.jupiter.api.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 edgeDE = g.addEdge("D", "E"); + + g.addEdge("E", "F"); + DefaultEdge edgeEG = 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(edgeDE), 1e-9); + assertEquals(7.5, ebc.getEdgeScore(edgeEG), 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 edgeDE = g.addEdge("D", "E"); + g.setEdgeWeight(edgeDE, 1000.0); // very large + + DefaultEdge edgeDF = g.addEdge("D", "F"); + + g.addEdge("E", "F"); + DefaultEdge edgeEG = g.addEdge("E", "G"); + DefaultEdge edgeFG = g.addEdge("F", "G"); + g.addEdge("F", "H"); + g.addEdge("G", "H"); + + EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>(g); + + assertEquals(0.0, ebc.getEdgeScore(edgeDE), 1e-9); + assertEquals(16.0, ebc.getEdgeScore(edgeDF), 1e-9); + assertEquals(1.5, ebc.getEdgeScore(edgeEG), 1e-9); + assertEquals(5.0, ebc.getEdgeScore(edgeFG), 1e-9); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EigenvectorCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EigenvectorCentralityTest.java new file mode 100644 index 00000000000..43572222c81 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/EigenvectorCentralityTest.java @@ -0,0 +1,182 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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.jupiter.api.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 + public void testNonExistantVertex() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testBadParameters1() + { + assertThrows(IllegalArgumentException.class, () -> { + final DirectedPseudograph g = + new DirectedPseudograph<>(DefaultEdge.class); + + new EigenvectorCentrality<>(g, -1); + }); + } + + @Test + public void testBadParameters2() + { + assertThrows(IllegalArgumentException.class, () -> { + final DirectedPseudograph g = + new DirectedPseudograph<>(DefaultEdge.class); + + new EigenvectorCentrality<>(g, 1, 0); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/HarmonicCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/HarmonicCentralityTest.java new file mode 100644 index 00000000000..c0524995f96 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/HarmonicCentralityTest.java @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/KatzCentralityTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/KatzCentralityTest.java new file mode 100644 index 00000000000..8192e0d2673 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/KatzCentralityTest.java @@ -0,0 +1,400 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.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 + public void testNonExistantVertex() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testBadParameters1() + { + assertThrows(IllegalArgumentException.class, () -> { + final DirectedPseudograph g = + new DirectedPseudograph<>(DefaultEdge.class); + + new KatzCentrality<>(g, -1, 100, 0.0001); + }); + } + + @Test + public void testBadParameters2() + { + assertThrows(IllegalArgumentException.class, () -> { + final DirectedPseudograph g = + new DirectedPseudograph<>(DefaultEdge.class); + + new KatzCentrality<>(g, 0.85, 0, 0.0001); + }); + } + + @Test + public void testBadParameters3() + { + assertThrows(IllegalArgumentException.class, () -> { + final DirectedPseudograph g = + new DirectedPseudograph<>(DefaultEdge.class); + + new KatzCentrality<>(g, 0.85, 100, 0.0); + }); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/PageRankTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/PageRankTest.java new file mode 100644 index 00000000000..4165a6d581d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/PageRankTest.java @@ -0,0 +1,288 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + + assertThrows(IllegalArgumentException.class, () -> pr.getVertexScore("unknown")); + } + + @Test + public void testBadParameters() + { + DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); + + assertThrows(IllegalArgumentException.class, () -> new PageRank<>(g, 1.1, 100, 0.0001)); + + assertThrows(IllegalArgumentException.class, () -> new PageRank<>(g, 0.85, 0, 0.0001)); + + assertThrows(IllegalArgumentException.class, () -> new PageRank<>(g, 0.85, 100, 0.0)); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristicTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristicTest.java new file mode 100644 index 00000000000..b3063ed4a87 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ALTAdmissibleHeuristicTest.java @@ -0,0 +1,149 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + } + } + } + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AStarShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AStarShortestPathTest.java new file mode 100644 index 00000000000..acb9edb36c0 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AStarShortestPathTest.java @@ -0,0 +1,106 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllDirectedPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllDirectedPathsTest.java new file mode 100644 index 00000000000..e42a457d943 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllDirectedPathsTest.java @@ -0,0 +1,289 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 = vertexSet(I1, I2); + Set targets = vertexSet(O1, O2); + + List> allPaths = + pathFindingAlg.getAllPaths(sources, targets, true, null); + + assertEquals(7, allPaths.size(), "Toy network should have correct number of simple paths"); + } + + @Test + public void testSmallExampleGraphWithPathValidator() + { + PathValidator pathValidator = + (partialPath, edge) -> !"B".equals(partialPath.getGraph().getEdgeTarget(edge)); + + AllDirectedPaths pathFindingAlg = + new AllDirectedPaths<>(toyGraph(), pathValidator); + + Set sources = vertexSet(I1, I2); + Set targets = vertexSet(O1, O2); + + List> allPaths = + pathFindingAlg.getAllPaths(sources, targets, true, null); + + assertEquals( + 3, allPaths.size(), + "Toy network should have correct number of simple paths using path validator"); + } + + @Test + public void testTrivialPaths() + { + // Verify fix for http://github.com/jgrapht/jgrapht/issues/234. + AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph()); + + Set sources = vertexSet(I1); + Set targets = vertexSet(I1, A); + + List> allPaths = + pathFindingAlg.getAllPaths(sources, targets, true, 1); + + assertEquals( + 2, allPaths.size(), "Toy network should have correct number of trivial simple paths"); + 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 testLengthOnePathsWithPathValidator() + { + DefaultDirectedGraph graph = + new DefaultDirectedGraph<>(DefaultEdge.class); + graph.addVertex("A"); + graph.addVertex("B"); + graph.addVertex("C"); + graph.addEdge("C", "A"); + graph.addEdge("C", "B"); + + PathValidator pathValidator = + (partialPath, edge) -> !"B".equals(graph.getEdgeTarget(edge)); + AllDirectedPaths all = new AllDirectedPaths<>(graph, pathValidator); + List> allPaths = + all.getAllPaths(graph.vertexSet(), graph.vertexSet(), true, graph.edgeSet().size()); + + assertEquals(4, allPaths.size()); + assertEquals(Arrays.asList("A"), allPaths.get(0).getVertexList()); + // The following is slightly counterintuitive, as one might think that the B-excluding + // pathValidator excludes + // this path. However, a PathValidator is designed to check additional edges, not vertices, + // so this is correct + // behavior. Included a comment on this in the Javadoc of the algorithm. + assertEquals(Arrays.asList("B"), allPaths.get(1).getVertexList()); + assertEquals(Arrays.asList("C"), allPaths.get(2).getVertexList()); + assertEquals(Arrays.asList("C", "A"), allPaths.get(3).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( + 3, allPaths.size(), "Example weighted graph has 3 paths of length no greater than 2"); + ; + + 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 = vertexSet(I1, I2); + Set targets = vertexSet(O1, O2); + + List> allPathsWithoutCycle = + pathFindingAlg.getAllPaths(sources, targets, true, 8); + + List> allPathsWithCycle = + pathFindingAlg.getAllPaths(sources, targets, false, 8); + + assertEquals( + 13, allPathsWithCycle.size(), + "Toy network with cycle should have correct number of paths with cycle"); + assertEquals( + 7, allPathsWithoutCycle.size(), + "Toy network with cycle should have correct number of simple paths"); + } + + @Test + public void testMustBoundIfNonSimplePaths() + { + // Goofy hack to test for an exception + + AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph()); + + Set sources = vertexSet(I1); + Set targets = vertexSet(O1); + + assertThrows(IllegalArgumentException.class, () -> pathFindingAlg.getAllPaths(sources, targets, false, null)); + } + + @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); + + assertFalse(paths.isEmpty(), "We should find at least some paths!"); + + paths.forEach( + path -> assertEquals( + 0, path.getLength(), + String.format( + "The path %s has length %d even though we requested only paths of length 0", + path, 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; + } + + private static HashSet vertexSet(String... vertices) + { + return new HashSet<>(Arrays.asList(vertices)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllPairsShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllPairsShortestPathsTest.java new file mode 100644 index 00000000000..926012c0a26 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/AllPairsShortestPathsTest.java @@ -0,0 +1,135 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.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++; + } + + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BFSShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BFSShortestPathTest.java new file mode 100644 index 00000000000..5ce776051f9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BFSShortestPathTest.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseHeuristicSearchTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseHeuristicSearchTest.java new file mode 100644 index 00000000000..b74c905e08c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseHeuristicSearchTest.java @@ -0,0 +1,224 @@ +/* + * (C) Copyright 2019-2023, 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseKShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseKShortestPathTest.java new file mode 100644 index 00000000000..568821a59d2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseKShortestPathTest.java @@ -0,0 +1,72 @@ +/* + * (C) Copyright 2019-2023, 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]); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPathsTest.java new file mode 100644 index 00000000000..eccabcb93ee --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BaseManyToManyShortestPathsTest.java @@ -0,0 +1,505 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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; + } + + protected void testNoPathMultiSet() + { + Graph graph = + new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); + graph.addVertex(1); + graph.addVertex(2); + graph.addVertex(3); + + ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = + getAlgorithm(graph).getManyToManyPaths(Set.of(1), Set.of(2, 3)); + + assertEquals(Double.POSITIVE_INFINITY, shortestPaths.getWeight(1, 2), 1e-9); + assertEquals(Double.POSITIVE_INFINITY, shortestPaths.getWeight(1, 3), 1e-9); + assertNull(shortestPaths.getPath(1, 2)); + assertNull(shortestPaths.getPath(1, 3)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPathTest.java new file mode 100644 index 00000000000..a06304ede97 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BellmanFordShortestPathTest.java @@ -0,0 +1,348 @@ +/* + * (C) Copyright 2006-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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()); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPathsTest.java new file mode 100644 index 00000000000..feec6017a27 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BhandariKDisjointShortestPathsTest.java @@ -0,0 +1,105 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPathTest.java new file mode 100644 index 00000000000..9545220a90f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalAStarShortestPathTest.java @@ -0,0 +1,227 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPathTest.java new file mode 100644 index 00000000000..df3a412f801 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/BidirectionalDijkstraShortestPathTest.java @@ -0,0 +1,421 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @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"); + + assertThrows(IllegalArgumentException.class, () -> new BidirectionalDijkstraShortestPath<>(g, -2.0).getPath("1", "2")); + + assertThrows(IllegalArgumentException.class, () -> new BidirectionalDijkstraShortestPath<>(g, 2.0).getPath("3", "2")); + + assertThrows(IllegalArgumentException.class, () -> new BidirectionalDijkstraShortestPath<>(g, 2.0).getPath("2", "3")); + + assertThrows(NullPointerException.class, () -> new BidirectionalDijkstraShortestPath<>(null).getPath("1", "1")); + + assertThrows(IllegalArgumentException.class, () -> new BidirectionalDijkstraShortestPath<>(g).getPath(null, "1")); + + assertThrows(IllegalArgumentException.class, () -> new BidirectionalDijkstraShortestPath<>(g).getPath("1", null)); + } + + 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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPathsTest.java new file mode 100644 index 00000000000..70ca9968691 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/CHManyToManyShortestPathsTest.java @@ -0,0 +1,178 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.ThreadPoolExecutor; + +import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + public static void shutdownExecutor() + throws InterruptedException + { + ConcurrencyUtil.shutdownExecutionService(executor); + } + + @Test + public void testEmptyGraph() + { + super.testEmptyGraph(); + } + + @Test + public void testSourcesIsNull() + { + assertThrows(NullPointerException.class, () -> super.testSourcesIsNull()); + } + + @Test + public void testTargetsIsNull() + { + assertThrows(NullPointerException.class, () -> 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstraTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstraTest.java new file mode 100644 index 00000000000..67a40a7bea1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyBidirectionalDijkstraTest.java @@ -0,0 +1,284 @@ +/* + * (C) Copyright 2019-2023, 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.shortestpath.ContractionHierarchyPrecomputation.*; +import org.jgrapht.generate.*; +import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + 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); + new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); + } + + @Test + public void testSourceNotPresent() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new SimpleWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(2); + ContractionHierarchyBidirectionalDijkstra dijkstra = + new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); + dijkstra.getPath(1, 2); + }); + } + + @Test + public void testTargetNotPresent() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputationTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputationTest.java new file mode 100644 index 00000000000..0d153f84e8a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ContractionHierarchyPrecomputationTest.java @@ -0,0 +1,573 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.ThreadPoolExecutor; + +import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + 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())); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPathsTest.java new file mode 100644 index 00000000000..5fc25f8eda9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DefaultManyToManyShortestPathsTest.java @@ -0,0 +1,95 @@ +/* + * (C) Copyright 2019-2023, 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 static org.junit.jupiter.api.Assertions.assertThrows; + +import org.jgrapht.*; +import org.jgrapht.alg.interfaces.*; +import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +/** + * Tests for {@link DefaultManyToManyShortestPaths}. + * + * @author Semen Chudakov + */ +public class DefaultManyToManyShortestPathsTest + extends BaseManyToManyShortestPathsTest +{ + @Test + public void testEmptyGraph() + { + super.testEmptyGraph(); + } + + @Test + public void testSourcesIsNull() + { + assertThrows(NullPointerException.class, () -> super.testSourcesIsNull()); + } + + @Test + public void testTargetsIsNull() + { + assertThrows(NullPointerException.class, () -> 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPathTest.java new file mode 100644 index 00000000000..d98b765909c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DeltaSteppingShortestPathTest.java @@ -0,0 +1,359 @@ +/* + * (C) Copyright 2018-2023, 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.*; +import org.jgrapht.generate.*; +import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + 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"; + + @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); + + DeltaSteppingShortestPath shortestPath = + new DeltaSteppingShortestPath<>(graph, executor); + assertThrows(IllegalArgumentException.class, () -> shortestPath.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 testSupplyComparator() + { + Graph graph = + new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); + String v0 = "v0"; + String v1 = "v1"; + String v2 = "v2"; + Graphs.addEdgeWithVertices(graph, v0, v1); + Graphs.addEdgeWithVertices(graph, v1, v2); + + DeltaSteppingShortestPath shortestPath = + new DeltaSteppingShortestPath<>(graph, executor); + GraphPath path = shortestPath.getPath(v0, v2); + + assertEquals(path.getWeight(), 2.0, 1e-9); + assertEquals(path.getVertexList(), Arrays.asList(v0, v1, v2)); + } + + @Test + public void testComparableVertices() + { + class ComparableVertex + implements Comparable + { + @Override + public int compareTo(ComparableVertex comparableVertex) + { + return 0; + } + } + ComparableVertex v1 = new ComparableVertex(); + ComparableVertex v2 = new ComparableVertex(); + + Graph graph = + new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); + Graphs.addAllVertices(graph, Arrays.asList(v1, v2)); + Graphs.addEdge(graph, v1, v2, 1.0); + + DeltaSteppingShortestPath shortestPath = + new DeltaSteppingShortestPath<>(graph, executor); + GraphPath path = shortestPath.getPath(v1, v2); + + assertEquals(path.getWeight(), 1.0, 1e-9); + assertEquals(path.getVertexList(), Arrays.asList(v1, v2)); + } + + @Test + public void testNonComparableVertices() + { + class NonComparableVertex + { + } + NonComparableVertex v1 = new NonComparableVertex(); + NonComparableVertex v2 = new NonComparableVertex(); + + Graph graph = + new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); + Graphs.addAllVertices(graph, Arrays.asList(v1, v2)); + Graphs.addEdge(graph, v1, v2, 1.0); + + DeltaSteppingShortestPath shortestPath = + new DeltaSteppingShortestPath<>(graph, executor); + GraphPath path = shortestPath.getPath(v1, v2); + + assertEquals(path.getWeight(), 1.0, 1e-9); + assertEquals(path.getVertexList(), Arrays.asList(v1, v2)); + } + + @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); + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIteratorTest.java new file mode 100644 index 00000000000..78f566d4788 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraClosestFirstIteratorTest.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPathsTest.java new file mode 100644 index 00000000000..bad8eca7665 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraManyToManyShortestPathsTest.java @@ -0,0 +1,102 @@ +/* + * (C) Copyright 2019-2023, 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 static org.junit.jupiter.api.Assertions.assertThrows; + +import org.jgrapht.*; +import org.jgrapht.alg.interfaces.*; +import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +/** + * Tests for {@link DijkstraManyToManyShortestPaths}. + * + * @author Semen Chudakov + */ +public class DijkstraManyToManyShortestPathsTest + extends BaseManyToManyShortestPathsTest +{ + + @Test + public void testEmptyGraph() + { + super.testEmptyGraph(); + } + + @Test + public void testSourcesIsNull() + { + assertThrows(NullPointerException.class, () -> super.testSourcesIsNull()); + } + + @Test + public void testTargetsIsNull() + { + assertThrows(NullPointerException.class, () -> super.testTargetsIsNull()); + } + + @Test + public void testNoPath() + { + super.testNoPath(); + } + + @Test + public void testNoPathMultiset() + { + super.testNoPathMultiSet(); + } + + @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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraShortestPathTest.java new file mode 100644 index 00000000000..42d8c9fb1a3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/DijkstraShortestPathTest.java @@ -0,0 +1,160 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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); + + assertThrows(IllegalArgumentException.class, () -> new DijkstraShortestPath<>(g).getPath(V1, V2)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPathTest.java new file mode 100644 index 00000000000..d3e744f66b5 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinKShortestPathTest.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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 + public void testNegativeK() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testNoSourceGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(2); + new EppsteinKShortestPath<>(graph).getPaths(1, 2, 1); + }); + } + + @Test + public void testNoSinkGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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]); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIteratorTest.java new file mode 100644 index 00000000000..1ff5eb3040f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/EppsteinShortestPathIteratorTest.java @@ -0,0 +1,423 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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 + public void testNoSourceGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(2); + new EppsteinShortestPathIterator<>(graph, 1, 2); + }); + } + + @Test + public void testNoSinkGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testNoPathLeft() + { + assertThrows(NoSuchElementException.class, () -> { + 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]); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallPseudographsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallPseudographsTest.java new file mode 100644 index 00000000000..184d85682e5 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallPseudographsTest.java @@ -0,0 +1,294 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test {@link FloydWarshallShortestPaths} on pseudo graphs. + * + * @author Dimitrios Michail + */ +public class FloydWarshallPseudographsTest +{ + + @Test + @Tag("slow") + 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)); + assertEquals(2, fw.getFirstHop(1, 3)); + assertEquals(2, fw.getFirstHop(1, 4)); + assertNull(fw.getFirstHop(2, 1)); + assertNull(fw.getFirstHop(2, 2)); + assertEquals(3, fw.getFirstHop(2, 3)); + assertEquals(3, fw.getFirstHop(2, 4)); + assertNull(fw.getFirstHop(3, 1)); + assertNull(fw.getFirstHop(3, 2)); + assertNull(fw.getFirstHop(3, 3)); + assertEquals(4, fw.getFirstHop(3, 4)); + 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)); + assertEquals(2, fw.getLastHop(1, 3)); + assertEquals(3, fw.getLastHop(1, 4)); + assertNull(fw.getLastHop(2, 1)); + assertNull(fw.getLastHop(2, 2)); + assertEquals(2, fw.getLastHop(2, 3)); + assertEquals(3, fw.getLastHop(2, 4)); + assertNull(fw.getLastHop(3, 1)); + assertNull(fw.getLastHop(3, 2)); + assertNull(fw.getLastHop(3, 3)); + assertEquals(3, fw.getLastHop(3, 4)); + 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()); + assertEquals(1, p1.getEndVertex()); + 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)); + + GraphPath p2 = fw.getPath(2, 2); + assertEquals(2, p2.getStartVertex()); + assertEquals(2, p2.getEndVertex()); + 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)); + + assertEquals(5.0, fw.getPath(1, 2).getWeight(), 1e-9); + assertEquals(15.0, fw.getPath(2, 1).getWeight(), 1e-9); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java new file mode 100644 index 00000000000..e61e6e19a8f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/FloydWarshallShortestPathsTest.java @@ -0,0 +1,165 @@ +/* + * (C) Copyright 2009-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @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")); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/GraphMeasurerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/GraphMeasurerTest.java new file mode 100644 index 00000000000..e9d8174d8e5 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/GraphMeasurerTest.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for GraphMeasurer + * + * @author Joris Kinable + * @author Alexandru Valeanu + */ +public class GraphMeasurerTest +{ + + private static 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPathTest.java new file mode 100644 index 00000000000..83438dae848 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/IntVertexDijkstraShortestPathTest.java @@ -0,0 +1,257 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + + assertThrows(IllegalArgumentException.class, () -> new IntVertexDijkstraShortestPath<>(g).getPath(1, 2)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/JohnsonShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/JohnsonShortestPathsTest.java new file mode 100644 index 00000000000..190eedfc3e2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/JohnsonShortestPathsTest.java @@ -0,0 +1,159 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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()); + } + } + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KDisjointShortestPathsTestCase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KDisjointShortestPathsTestCase.java new file mode 100644 index 00000000000..f591f0ff271 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KDisjointShortestPathsTestCase.java @@ -0,0 +1,1072 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * 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(2, pathList.get(0).getEndVertex()); + assertEquals(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()); + + assertEquals(expectedPaths.size(), resultPaths.size()); + for (int[] expectedPath : expectedPaths) { + boolean isIncluded = + resultPaths.stream().anyMatch(result -> Arrays.equals(result, expectedPath)); + 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); + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/KSPExample.png b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KSPExample.png similarity index 100% rename from jgrapht-core/src/test/java/org/jgrapht/alg/KSPExample.png rename to jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KSPExample.png diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsTest.java new file mode 100644 index 00000000000..7b495050b3a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ListSingleSourcePathsTest.java @@ -0,0 +1,69 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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()); + 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/MartinShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/MartinShortestPathTest.java new file mode 100644 index 00000000000..2279362f17f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/MartinShortestPathTest.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ShortestPathTestCase.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ShortestPathTestCase.java new file mode 100644 index 00000000000..2f964a18afb --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/ShortestPathTestCase.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/SuurballeKDisjointShortestPathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/SuurballeKDisjointShortestPathsTest.java new file mode 100644 index 00000000000..cf45c891487 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/SuurballeKDisjointShortestPathsTest.java @@ -0,0 +1,82 @@ +/* + * (C) Copyright 2018-2023, 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 static org.junit.jupiter.api.Assertions.*; + +import java.util.*; + +import org.jgrapht.*; +import org.jgrapht.alg.interfaces.*; +import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; + +/** + * + * Tests for the {@link SuurballeKDisjointShortestPaths} class. + * + * @author Assaf Mizrachi + */ +public class SuurballeKDisjointShortestPathsTest + extends KDisjointShortestPathsTestCase +{ + + @Override + protected KShortestPathAlgorithm getKShortestPathAlgorithm(Graph graph) + { + return new SuurballeKDisjointShortestPaths<>(graph); + } + + @Test + public void testTwoDisjointPathsInGraphContainingFractionalDoublelEdgeWeight() + { + DefaultDirectedWeightedGraph graph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + 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(e12, 1.0); + graph.setEdgeWeight(e13, 2.0); + graph.setEdgeWeight(e23, 0.9); + + SuurballeKDisjointShortestPaths alg = + new SuurballeKDisjointShortestPaths(graph); + + List> pathList = alg.getPaths(1, 3, 2); + + assertEquals(2, pathList.size()); + + GraphPath expectedP1 = + new GraphWalk<>(graph, Arrays.asList(1, 2, 3), 1); + assertEquals(expectedP1, pathList.get(0)); + assertEquals(2, pathList.get(0).getLength()); + assertEquals(1.9, pathList.get(0).getWeight(), 0.0); + + GraphPath expectedP2 = + new GraphWalk<>(graph, Arrays.asList(1, 3), 2); + assertEquals(expectedP2, pathList.get(1)); + assertEquals(1, pathList.get(1).getLength()); + assertEquals(2.0, pathList.get(1).getWeight(), 0.0); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputationTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputationTest.java new file mode 100644 index 00000000000..6febd7e1c10 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingPrecomputationTest.java @@ -0,0 +1,377 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + 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()); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPathTest.java new file mode 100644 index 00000000000..c29a42877a7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TransitNodeRoutingShortestPathTest.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2020-2023, 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.shortestpath.ContractionHierarchyPrecomputation.*; +import org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.*; +import org.jgrapht.generate.*; +import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import java.util.concurrent.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeAll + public static void createExecutor() + { + executor = + ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); + } + + @AfterAll + 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 + @SuppressWarnings("unused") + 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)); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeMeasurerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeMeasurerTest.java new file mode 100644 index 00000000000..13785e0f4a5 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeMeasurerTest.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsTest.java new file mode 100644 index 00000000000..8968ca0384f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeSingleSourcePathsTest.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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()); + 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenKShortestPathTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenKShortestPathTest.java new file mode 100644 index 00000000000..1d049d59898 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenKShortestPathTest.java @@ -0,0 +1,285 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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 + public void testNegativeK() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testNoSourceGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(2); + new YenKShortestPath<>(graph).getPaths(1, 2, 1); + }); + + } + + @Test + public void testNoSinkGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenShortestPathIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenShortestPathIteratorTest.java new file mode 100644 index 00000000000..acb184e6f31 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/YenShortestPathIteratorTest.java @@ -0,0 +1,445 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for the {@link YenShortestPathIterator}. + */ +public class YenShortestPathIteratorTest + extends BaseKShortestPathTest +{ + @Test + public void testNoSourceGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(2); + new YenShortestPathIterator<>(graph, 1, 2); + }); + } + + @Test + public void testNoSinkGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testNoPathLeft() + { + assertThrows(NoSuchElementException.class, () -> { + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistanceTest.java new file mode 100644 index 00000000000..16ed9dac35b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/similarity/ZhangShashaTreeEditDistanceTest.java @@ -0,0 +1,223 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.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.jupiter.api.Assertions.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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTreeTest.java new file mode 100644 index 00000000000..55b949239b7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/AhujaOrlinSharmaCapacitatedMinimumSpanningTreeTest.java @@ -0,0 +1,845 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +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 { + 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 { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTreeTest.java new file mode 100644 index 00000000000..7b487110240 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/BoruvkaMinimumSpanningTreeTest.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTreeTest.java new file mode 100644 index 00000000000..c43a76cb18e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/EsauWilliamsCapacitatedMinimumSpanningTreeTest.java @@ -0,0 +1,381 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +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; + + assertThrows( + IllegalArgumentException.class, + () -> new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, capacity, demands, 1)); + } + + /** + * 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; + + assertThrows( + IllegalArgumentException.class, + () -> new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, capacity, demands, 1)); + } + + /** + * 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpannerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpannerTest.java new file mode 100644 index 00000000000..dd64f3ea77c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/GreedyMultiplicativeSpannerTest.java @@ -0,0 +1,378 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @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); + + assertThrows(IllegalArgumentException.class, () -> new GreedyMultiplicativeSpanner<>(g, 2).getSpanner(), "Negative edge weights not permitted."); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTreeTest.java new file mode 100644 index 00000000000..adf260db369 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/KruskalMinimumSpanningTreeTest.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/MinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/MinimumSpanningTreeTest.java new file mode 100644 index 00000000000..336017d0fa6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/MinimumSpanningTreeTest.java @@ -0,0 +1,177 @@ +/* + * (C) Copyright 2010-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTreeTest.java new file mode 100644 index 00000000000..335fdba8b54 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/PrimMinimumSpanningTreeTest.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithmTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithmTest.java new file mode 100644 index 00000000000..ddd0c55d36e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/steiner/KouMarkowskyBermanAlgorithmTest.java @@ -0,0 +1,153 @@ +/* + * (C) Copyright 2025, by Lena Büttel and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ +package org.jgrapht.alg.steiner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jgrapht.Graph; +import org.jgrapht.alg.interfaces.SteinerTreeAlgorithm.SteinerTree; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleWeightedGraph; + +import org.jgrapht.graph.builder.GraphTypeBuilder; +import org.jgrapht.util.SupplierUtil; +import org.jgrapht.generate.GnpRandomGraphGenerator; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class KouMarkowskyBermanAlgorithmTest +{ + + @Test + public void testExampleGraphSteinerTree() + { + List exampleVertices = + Arrays.asList("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9"); + SimpleWeightedGraph exampleGraph = + new SimpleWeightedGraph<>(DefaultWeightedEdge.class); + + for (String v : exampleVertices) { + exampleGraph.addVertex(v); + } + + // Add edges with weights + setEdgeWithWeight(exampleGraph, "v1", "v2", 10); + setEdgeWithWeight(exampleGraph, "v2", "v3", 8); + setEdgeWithWeight(exampleGraph, "v3", "v4", 9); + setEdgeWithWeight(exampleGraph, "v4", "v5", 2); + setEdgeWithWeight(exampleGraph, "v3", "v5", 2); + setEdgeWithWeight(exampleGraph, "v6", "v5", 1); + setEdgeWithWeight(exampleGraph, "v9", "v5", 1); + setEdgeWithWeight(exampleGraph, "v2", "v6", 1); + setEdgeWithWeight(exampleGraph, "v6", "v7", 1.0); + setEdgeWithWeight(exampleGraph, "v1", "v9", 1); + setEdgeWithWeight(exampleGraph, "v7", "v8", 0.5); + setEdgeWithWeight(exampleGraph, "v8", "v9", 0.5); + + Set terminals = new HashSet<>(Arrays.asList("v1", "v2", "v3", "v4")); + + KouMarkowskyBermanAlgorithm steinerAlg = + new KouMarkowskyBermanAlgorithm<>(exampleGraph); + + SteinerTree steinerTree = steinerAlg.getSteinerTree(terminals); + + assertEquals(8.0, steinerTree.getWeight(), 0.001); + + Set exampleTreeVertices = + steinerTree.getEdges().stream().flatMap((DefaultWeightedEdge e) -> { + String source = exampleGraph.getEdgeSource(e); + String target = exampleGraph.getEdgeTarget(e); + return Stream.of(source, target); + }).collect(Collectors.toSet()); + + for (String terminal : terminals) { + assertTrue(exampleTreeVertices.contains(terminal), "Missing terminal: " + terminal); + } + + assertEquals(exampleTreeVertices.size() - 1, steinerTree.getEdges().size()); + + } + + @Test + public void testRandomGraphSteinerTree() + { + + Graph gnpGraph = GraphTypeBuilder + .undirected().weighted(true).edgeClass(DefaultWeightedEdge.class) + .vertexSupplier(SupplierUtil.createStringSupplier()).buildGraph(); + + GnpRandomGraphGenerator gnpRandomGraphGenerator = + new GnpRandomGraphGenerator<>(25, 0.5); + gnpRandomGraphGenerator.generateGraph(gnpGraph); + + Random rand = new Random(); + + for (DefaultWeightedEdge edge : gnpGraph.edgeSet()) { + double weight = rand.nextInt(10) + ((1.0 + rand.nextInt(10)) / 10); + gnpGraph.setEdgeWeight(edge, weight); + } + + KouMarkowskyBermanAlgorithm steinerAlg = + new KouMarkowskyBermanAlgorithm<>(gnpGraph); + + List shuffled = new ArrayList<>(gnpGraph.vertexSet()); + Collections.shuffle(shuffled); + + Set selected = new HashSet<>(shuffled.subList(0, 10)); + + SteinerTree steinerTree = steinerAlg.getSteinerTree(selected); + + Set gnpTreeVertices = + steinerTree.getEdges().stream().flatMap((DefaultWeightedEdge e) -> { + String source = gnpGraph.getEdgeSource(e); + String target = gnpGraph.getEdgeTarget(e); + return Stream.of(source, target); + }).collect(Collectors.toSet()); + + for (String vertex : selected) { + assertTrue(gnpTreeVertices.contains(vertex), "Missing terminal: " + vertex); + } + + assertEquals(gnpTreeVertices.size() - 1, steinerTree.getEdges().size()); + + } + + private void setEdgeWithWeight( + Graph graph, String source, String target, double weight) + { + graph.addVertex(source); + graph.addVertex(target); + DefaultWeightedEdge edge = graph.addEdge(source, target); + if (edge != null) { + graph.setEdgeWeight(edge, weight); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSPTest.java new file mode 100644 index 00000000000..e5ced76767e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/ChristofidesThreeHalvesApproxMetricTSPTest.java @@ -0,0 +1,187 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for the {@link ChristofidesThreeHalvesApproxMetricTSP} + * + * @author Timofey Chudakov + */ +public class ChristofidesThreeHalvesApproxMetricTSPTest +{ + + /** + * Directed graph + */ + @Test + public void testGetTour0() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + Graphs.addEdgeWithVertices(graph, 1, 2, 5); + ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = + new ChristofidesThreeHalvesApproxMetricTSP<>(); + approxMetricTSP.getTour(graph); + }); + } + + /** + * Empty graph + */ + @Test + public void testGetTour1() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = + new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); + ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = + new ChristofidesThreeHalvesApproxMetricTSP<>(); + approxMetricTSP.getTour(graph); + }); + } + + /** + * Not complete + */ + @Test + public void testGetTour2() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSPTest.java new file mode 100644 index 00000000000..69a0ebc26d0 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/FarthestInsertionHeuristicTSPTest.java @@ -0,0 +1,207 @@ +/* + * (C) Copyright 2021-2024, by J. Alejandro Cornejo-Acosta and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.Graphs; +import org.jgrapht.TestUtil; +import org.jgrapht.graph.*; +import org.jgrapht.graph.builder.GraphTypeBuilder; +import org.junit.jupiter.api.*; + +import java.util.List; + +import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link FarthestInsertionHeuristicTSP} + * + * @author J. Alejandro Cornejo-Acosta + */ +public class FarthestInsertionHeuristicTSPTest +{ + /** + * Directed graph + */ + @Test + public void testDirectedGraph() + { + Graph graph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); + Graphs.addEdgeWithVertices(graph, 1, 2, 5); + FarthestInsertionHeuristicTSP farthestInsertion = + new FarthestInsertionHeuristicTSP<>(); + + assertThrows(IllegalArgumentException.class, () -> { + farthestInsertion.getTour(graph); + }); + + } + + /** + * Empty graph + */ + @Test + public void testEmptyGraph() + { + Graph graph = + new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); + FarthestInsertionHeuristicTSP farthestInsertion = + new FarthestInsertionHeuristicTSP<>(); + assertThrows(IllegalArgumentException.class, () -> { + farthestInsertion.getTour(graph); + }); + } + + /** + * Not complete + */ + @Test + public void testNoCompleteGraph() + { + Graph graph = + new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); + graph.addVertex(0); + graph.addVertex(1); + FarthestInsertionHeuristicTSP farthestInsertion = + new FarthestInsertionHeuristicTSP<>(); + assertThrows(IllegalArgumentException.class, () -> { + farthestInsertion.getTour(graph); + }); + } + + /** + * There is only one tour + */ + @Test + public void testGetTour1() + { + int[][] edges = {{1, 2, 5}}; + Graph graph = TestUtil.createUndirected(edges); + + FarthestInsertionHeuristicTSP farthestInsertion = + new FarthestInsertionHeuristicTSP<>(); + GraphPath tour = farthestInsertion.getTour(graph); + assertHamiltonian(graph, tour); + assertEquals(10, tour.getWeight(), 1e-9); + } + + /** + * There is only one tour + */ + @Test + public void testGetTour2() + { + int[][] edges = {{1, 2, 5}, {1, 3, 5}, {2, 3, 9},}; + Graph graph = TestUtil.createUndirected(edges); + + FarthestInsertionHeuristicTSP farthestInsertion = + new FarthestInsertionHeuristicTSP<>(); + GraphPath tour = farthestInsertion.getTour(graph); + assertHamiltonian(graph, tour); + assertEquals(19, tour.getWeight(), 1e-9); + } + + /** + * Test with dummy graph of five vertices + */ + @Test + public void testDummyGraph5() + { + int[][] allDist = {{0, 8, 10, 11, 15}, + {8, 0, 2, 3, 7}, + {10, 2, 0, 1, 5}, + {11, 3, 1, 0, 4}, + {15, 7, 5, 4, 0} + }; + Graph graph = createGraphFromMatrixDistances(allDist); + var farthestInsH = new FarthestInsertionHeuristicTSP(); + + var tour = farthestInsH.getTour(graph); + assertEquals(30, tour.getWeight(), 1e-9); + assertArrayEquals(new Integer[]{3, 2, 1, 0, 4, 3}, + tour.getVertexList().toArray(new Integer[0])); + } + + @Test + public void testDummyGraph5WithSubtour() + { + int[][] allDist = {{0, 8, 10, 11, 15}, + {8, 0, 2, 3, 7}, + {10, 2, 0, 1, 5}, + {11, 3, 1, 0, 4}, + {15, 7, 5, 4, 0} + }; + Graph graph = createGraphFromMatrixDistances(allDist); + var farthestInsH = new FarthestInsertionHeuristicTSP + (new GraphWalk<>(graph, List.of(3, 2, 0, 4), -1)); + + var tour = farthestInsH.getTour(graph); + assertEquals(30, tour.getWeight(), 1e-9); + + // vertex 1 should be inserted between vertices 2 and 0 + assertArrayEquals(new Integer[]{3, 2, 1, 0, 4, 3}, + tour.getVertexList().toArray(new Integer[0])); + } + + /** + * Test with dummy graph of ten vertices + */ + @Test + public void testDummyGraph10() + { + int[][] allDist = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {1, 0, 10, 11, 12, 13, 14, 15, 16, 17}, + {2, 10, 0, 18, 19, 20, 21, 22, 23, 24}, + {3, 11, 18, 0, 25, 26, 27, 28, 29, 30}, + {4, 12, 19, 25, 0, 31, 32, 33, 34, 35}, + {5, 13, 20, 26, 31, 0, 36, 37, 38, 39}, + {6, 14, 21, 27, 32, 36, 0, 40, 41, 42}, + {7, 15, 22, 28, 33, 37, 40, 0, 43, 44}, + {8, 16, 23, 29, 34, 38, 41, 43, 0, 45}, + {9, 17, 24, 30, 35, 39, 42, 44, 45, 0}}; + + Graph graph = createGraphFromMatrixDistances(allDist); + var farthestInsertion = new FarthestInsertionHeuristicTSP(); + var tour = farthestInsertion.getTour(graph); + assertEquals(210, tour.getWeight(), 1e-9); + assertArrayEquals(new Integer[]{4, 5, 1, 6, 0, 7, 3, 8, 2, 9, 4}, + tour.getVertexList().toArray(new Integer[0])); + } + + // utilities + static Graph createGraphFromMatrixDistances(int[][] allDist) + { + int n = allDist.length; + var graph = GraphTypeBuilder + .undirected().allowingMultipleEdges(false) + .allowingSelfLoops(false).edgeClass(DefaultWeightedEdge.class).weighted(true).buildGraph(); + + // add edges + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (i != j) { + Graphs.addEdgeWithVertices(graph, i, j, allDist[i][j]); + } + } + } + return graph; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/GeometricTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/GeometricTSPTest.java new file mode 100644 index 00000000000..44b106e4fc0 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/GeometricTSPTest.java @@ -0,0 +1,147 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +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 + */ +@Tag("slow") +public class GeometricTSPTest +{ + + private static final OfDouble RNG = new Random().doubles(0.0, 100.0).iterator(); + + public static List graphs() + { + List graphs = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + int size = (int) Math.pow(10, i); + graphs.add(Arguments.of( generate(size), size )); + } + return graphs; + } + + 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( + Graph graph, HamiltonianCycleAlgorithm algorithm) + { + GraphPath tour = algorithm.getTour(graph); + assertHamiltonian(graph, tour); + } + + @DisplayName("Greedy") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testGreedy(Graph graph, int size) + { + testWith(graph, new GreedyHeuristicTSP<>()); + } + + @DisplayName("Nearest insertion starting from shortest edge") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testNearestInsertionHeuristic(Graph graph, int size) + { + testWith(graph, new NearestInsertionHeuristicTSP<>()); + } + + @DisplayName("Nearest neighbour") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testNearestNeighbourHeuristic(Graph graph, int size) + { + testWith(graph, new NearestNeighborHeuristicTSP<>()); + } + + @DisplayName("Random") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testRandom(Graph graph, int size) + { + testWith(graph, new RandomTourTSP<>()); + } + + @DisplayName("Two-opt of nearest neighbour") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testTwoOptNearestNeighbour(Graph graph, int size) + { + testWith( + graph, + new TwoOptHeuristicTSP<>(new NearestNeighborHeuristicTSP<>())); + } + + @DisplayName("Two-opt, 1 attempt from random") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testTwoOpt1(Graph graph, int size) + { + testWith(graph, new TwoOptHeuristicTSP<>(1)); + } + + @DisplayName("Greedy") + @ParameterizedTest(name = "{1} points") + @MethodSource("graphs") + public void testChristofides(Graph graph, int size) + { + testWith(graph, new ChristofidesThreeHalvesApproxMetricTSP<>()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/HeldKarpTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/HeldKarpTSPTest.java new file mode 100644 index 00000000000..60678d1fb3d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/HeldKarpTSPTest.java @@ -0,0 +1,382 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link HeldKarpTSP} + * + * @author Alexandru Valeanu + * + */ +@Tag("slow") +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 + public void testInvalidInstanceEmpty() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 numTests = 500; + Random random = new Random(123); + + for (int test = 0; test < numTests; 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSPTest.java new file mode 100644 index 00000000000..932546fb632 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/NearestNeighborHeuristicTSPTest.java @@ -0,0 +1,199 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +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; + + @BeforeAll + 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/PalmerHamiltonianCycleTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/PalmerHamiltonianCycleTest.java new file mode 100644 index 00000000000..cd9baa130de --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/PalmerHamiltonianCycleTest.java @@ -0,0 +1,232 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testLineGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 numTests = 500; + for (int test = 0; test < numTests; 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 + @Tag("slow") + public void testRandomGraphs() + { + testRandomGraphs(new Random(0xC0FFEE)); + } + + private void testRandomGraphs2(Random random) + { + final int numTests = 500; + for (int test = 0; test < numTests; 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 + @Tag("slow") + public void testRandomGraphs2FixedSeed() + { + testRandomGraphs2(new Random(0xBEEF)); + } + + private static Graph bigGraph = new SimpleGraph<>(DefaultEdge.class); + + @BeforeAll + 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 + @Tag("slow") + public void testBigGraph() + { + GraphPath tour = + new PalmerHamiltonianCycle().getTour(bigGraph); + + assertNotNull(tour); + assertHamiltonian(bigGraph, tour); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoApproxMetricTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoApproxMetricTSPTest.java new file mode 100644 index 00000000000..2c77a42c7c4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoApproxMetricTSPTest.java @@ -0,0 +1,197 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testInvalidInstanceDirected() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); + g.addVertex("A"); + + new TwoApproxMetricTSP().getTour(g); + }); + } + + @Test + public void testInvalidInstanceNotComplete() + { + assertThrows(IllegalArgumentException.class, () -> { + 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoOptHeuristicTSPTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoOptHeuristicTSPTest.java new file mode 100644 index 00000000000..c57a730bc35 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoOptHeuristicTSPTest.java @@ -0,0 +1,143 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for {@link TwoOptHeuristicTSP}. + * + * @author Dimitrios Michail + */ +@Tag("slow") +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 + public void testInvalidInstanceDirected() + { + assertThrows(IllegalArgumentException.class, () -> new TwoOptHeuristicTSP() + .getTour(new SimpleDirectedGraph<>(DefaultEdge.class))); + + } + + @Test + public void testInvalidInstanceNotComplete() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/transform/LineGraphConverterTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/transform/LineGraphConverterTest.java new file mode 100644 index 00000000000..f136673173d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/transform/LineGraphConverterTest.java @@ -0,0 +1,183 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/AliasMethodSamplerTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/AliasMethodSamplerTest.java new file mode 100644 index 00000000000..22ea6649b46 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/AliasMethodSamplerTest.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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 + public void testNonValid() + { + assertThrows(IllegalArgumentException.class, () -> { + double[] prob = { 0.5, 0.6 }; + new AliasMethodSampler(prob, new Random(15)); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/AllAlgUtilTests.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/AllAlgUtilTests.java deleted file mode 100644 index a7a3673de13..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/util/AllAlgUtilTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * AllAlgTests.java - * ---------------- - * (C) Copyright 2010-2010, by Tom Conerly and Contributors. - * - * Original Author: Tom Conerly - * Contributor(s): - * - * Changes - * ------- - * 2-Feb-2010 : Initial revision (TC); - * - */ -package org.jgrapht.alg.util; - -import junit.framework.*; - - -/** - * A TestSuite for all tests in this package. - * - * @author Tom Conerly - */ -public final class AllAlgUtilTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllAlgUtilTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite for all tests in this package. - * - * @return a test suite for all tests in this package. - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // $JUnit-BEGIN$ - suite.addTest(new TestSuite(UnionFindTest.class)); - - // $JUnit-END$ - return suite; - } -} - -// End AllAlgUtilTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/FixedSizeIntegerQueueTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/FixedSizeIntegerQueueTest.java new file mode 100644 index 00000000000..22588171abd --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/FixedSizeIntegerQueueTest.java @@ -0,0 +1,53 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/NeighborCacheTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/NeighborCacheTest.java new file mode 100644 index 00000000000..897196632ff --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/NeighborCacheTest.java @@ -0,0 +1,218 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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"; + + // ~ 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()); + assertTrue(neighbors1.contains(V2)); + + g.addVertex(V3); + g.addEdge(V3, V1); + + Set neighbors3 = cache.neighborsOf(V3); + + assertEquals(2, neighbors1.size()); + assertTrue(neighbors1.contains(V3)); + + assertEquals(1, neighbors3.size()); + assertTrue(neighbors3.contains(V1)); + + g.removeEdge(V3, V1); + + assertEquals(1, neighbors1.size()); + assertFalse(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()); + assertTrue(s.contains(V2)); + + g.addVertex(V3); + g.addEdge(V3, V1); + + Set q = index.successorsOf(V3); + + assertEquals(1, p.size()); + assertEquals(1, s.size()); + assertTrue(p.contains(V3)); + + assertEquals(1, q.size()); + assertTrue(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); + + assertThrows(IllegalArgumentException.class, () -> cache.neighborListOf(b)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/PairTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/PairTest.java new file mode 100644 index 00000000000..d27da7b3811 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/PairTest.java @@ -0,0 +1,264 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/UnionFindTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/UnionFindTest.java index 8f59c063bb4..29ca43a2a7e 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/alg/util/UnionFindTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/UnionFindTest.java @@ -1,46 +1,27 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2010, by Barak Naveh and Contributors. +/* + * (C) Copyright 2010-2023, by Tom Conerly and Contributors. * - * 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. - */ -/* ------------------------------ - * UnionFindTest.java - * ------------------------------ - * (C) Copyright 2010-2010, by Tom Conerly and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Tom Conerly - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 02-Feb-2010 : Initial revision (TC); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/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 org.junit.jupiter.api.*; -import junit.framework.*; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; /** * . @@ -48,17 +29,17 @@ * @author Tom Conerly */ public class UnionFindTest - extends TestCase { - //~ Methods ---------------------------------------------------------------- + // ~ Methods ---------------------------------------------------------------- /** * . */ + @Test public void testUnionFind() { TreeSet set = new TreeSet(); - String [] strs = { "aaa", "bbb", "ccc", "ddd", "eee" }; + String[] strs = { "aaa", "bbb", "ccc", "ddd", "eee" }; ArrayList> sets = new ArrayList>(); for (String str : strs) { set.add(str); @@ -66,27 +47,42 @@ public void testUnionFind() 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) @@ -120,9 +116,7 @@ private boolean same(ArrayList> sets, String a, String b) } private void testIdentical( - String [] universe, - ArrayList> sets, - UnionFind uf) + String[] universe, ArrayList> sets, UnionFind uf) { for (String a : universe) { for (String b : universe) { @@ -133,5 +127,3 @@ private void testIdentical( } } } - -// End UnionFindTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/util/VertexDegreeComparatorTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/util/VertexDegreeComparatorTest.java new file mode 100644 index 00000000000..950d318e888 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/util/VertexDegreeComparatorTest.java @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for VertexDegreeComparator + * + * @author Joris Kinable + */ +public class VertexDegreeComparatorTest +{ + + protected static final int TEST_REPEATS = 20; + + private final GraphGenerator randomGraphGenerator = + new GnmRandomGraphGenerator<>(100, 1000, 0); + + @RepeatedTest(TEST_REPEATS) + public void testVertexDegreeComparator() + { + 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 + vertices.sort(VertexDegreeComparator.of(graph)); + + for (int i = 0; i < vertices.size() - 1; i++) { + assertTrue(graph.degreeOf(vertices.get(i)) <= graph.degreeOf(vertices.get(i + 1))); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImplTest.java new file mode 100644 index 00000000000..5c7844bca21 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/BarYehudaEvenTwoApproxVCImplTest.java @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImplTest.java new file mode 100644 index 00000000000..8572b657dea --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/ClarksonTwoApproxVCImplTest.java @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImplTest.java new file mode 100644 index 00000000000..63126353477 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/EdgeBasedTwoApproxVCImplTest.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/GreedyVCImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/GreedyVCImplTest.java new file mode 100644 index 00000000000..f8b4c8179ac --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/GreedyVCImplTest.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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. + */ + @RepeatedTest(TEST_REPEATS) + public void testFindGreedyCover() + { + 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. + */ + @RepeatedTest(TEST_REPEATS) + public void testFindGreedyWeightedCover() + { + + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImplTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImplTest.java new file mode 100644 index 00000000000..a8a86bab45b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/RecursiveExactVCImplTest.java @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2018-2023, 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverExactTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverExactTest.java new file mode 100644 index 00000000000..4e61da4046a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverExactTest.java @@ -0,0 +1,373 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.isCover; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTest.java new file mode 100644 index 00000000000..3a4d7c4b951 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTest.java @@ -0,0 +1,32 @@ +/* + * (C) Copyright 2003-2023, 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); +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTestUtils.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTestUtils.java new file mode 100644 index 00000000000..2e8b2a73f98 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTestUtils.java @@ -0,0 +1,84 @@ +/* + * (C) Copyright 2003-2023, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTwoApproxTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTwoApproxTest.java new file mode 100644 index 00000000000..6162279222e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTwoApproxTest.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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. + */ + @RepeatedTest(TEST_REPEATS) + public void testFind2ApproximationCover() + { + 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 + */ + @RepeatedTest(TEST_REPEATS) + public void testFind2ApproximationCover2() + { + + 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 + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverExactTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverExactTest.java new file mode 100644 index 00000000000..ad9a55850a9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverExactTest.java @@ -0,0 +1,384 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the weighted exact vertex cover algorithms. + * + * @author Joris Kinable + */ +public abstract class WeightedVertexCoverExactTest + extends VertexCoverExactTest + implements WeightedVertexCoverTest +{ + + // ------- Exact algorithms ------ + + @Test + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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 + @Tag("slow") + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTest.java new file mode 100644 index 00000000000..713255f719f --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTest.java @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTwoApproxTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTwoApproxTest.java new file mode 100644 index 00000000000..a85003035f8 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/WeightedVertexCoverTwoApproxTest.java @@ -0,0 +1,61 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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. + */ + @RepeatedTest(TEST_REPEATS) + public void testFind2ApproximationWeightedCover() + { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/GraphReaderTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/GraphReaderTest.java deleted file mode 100644 index 6ad360c10d6..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/GraphReaderTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * GraphReaderTest.java - * ------------------- - * (C) Copyright 2010-2010, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Dec-2008 : Initial revision (AN); - * - */ -package org.jgrapht.experimental; - -import java.io.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Michael Behrisch - */ -public class GraphReaderTest - extends TestCase -{ - //~ Instance fields -------------------------------------------------------- - - String _unweighted = "p 3\ne 1 2\ne 1 3\n"; - String _weighted = "p 3\ne 1 2 .5\ne 1 3 7\n"; - - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testGraphReader() - { - GraphReader reader; - try { - reader = - new GraphReader( - new StringReader(_unweighted)); - Graph g = - new SimpleGraph( - DefaultEdge.class); - VertexFactory vf = new IntVertexFactory(); - reader.generateGraph(g, vf, null); - Graph g2 = - new SimpleGraph( - DefaultEdge.class); - g2.addVertex(0); - g2.addVertex(1); - g2.addVertex(2); - g2.addEdge(0, 1); - g2.addEdge(0, 2); - assertEquals(g2.toString(), g.toString()); - } catch (IOException e) { - } - } - - /** - * . - */ - public void testGraphReaderWeighted() - { - try { - GraphReader reader = - new GraphReader( - new StringReader(_weighted), - 1); - Graph g = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); - VertexFactory vf = new IntVertexFactory(); - reader.generateGraph(g, vf, null); - WeightedGraph g2 = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); - g2.addVertex(0); - g2.addVertex(1); - g2.addVertex(2); - g2.setEdgeWeight(g2.addEdge(0, 1), .5); - g2.setEdgeWeight(g2.addEdge(0, 2), 7); - assertEquals(g2.toString(), g.toString()); - } catch (IOException e) { - } - } - - //~ Inner Classes ---------------------------------------------------------- - - private static final class IntVertexFactory - implements VertexFactory - { - int last = 0; - - public Integer createVertex() - { - return last++; - } - } -} - -// End GraphReaderTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/alg/ColoringTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/alg/ColoringTest.java deleted file mode 100644 index bdf2b12f967..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/alg/ColoringTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * ColoringTest.java - * ---------------- - * (C) Copyright 2010, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 17-Feb-2008 : Initial revision (MB); - * - */ -package org.jgrapht.experimental.alg; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.alg.color.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Michael Behrisch - */ -public class ColoringTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testGreedyColoring() - { - Graph completeGraph = - new SimpleGraph( - DefaultEdge.class); - CompleteGraphGenerator completeGraphGenerator = - new CompleteGraphGenerator( - 6); - completeGraphGenerator.generateGraph( - completeGraph, - new ClassBasedVertexFactory(Object.class), - null); - GreedyColoring colorer = - new GreedyColoring(completeGraph); - assertEquals(new Integer(6), colorer.getUpperBound(null)); - } - - /** - * . - */ - public void testBacktrackColoring() - { - Graph completeGraph = - new SimpleGraph( - DefaultEdge.class); - CompleteGraphGenerator completeGraphGenerator = - new CompleteGraphGenerator( - 6); - completeGraphGenerator.generateGraph( - completeGraph, - new ClassBasedVertexFactory(Object.class), - null); - BrownBacktrackColoring colorer = - new BrownBacktrackColoring(completeGraph); - assertEquals(new Integer(6), colorer.getResult(null)); - } -} - -// End ColoringTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/dag/DirectedAcyclicGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/dag/DirectedAcyclicGraphTest.java deleted file mode 100644 index 6ee1e635186..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/dag/DirectedAcyclicGraphTest.java +++ /dev/null @@ -1,603 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * DirectedAcyclicGraphTest.java - * ------------------- - * (C) Copyright 2008-2008, by Peter Giles and Contributors. - * - * Original Author: Peter Giles - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 17-Mar-2008 : Initial revision (PG); - * - */ -package org.jgrapht.experimental.dag; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.alg.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; -import org.jgrapht.traverse.*; - - -/** - * Unit tests for the DirectedAcyclicGraph, a dynamic DAG implementation. - * - * @author gilesp@u.washington.edu - */ -public class DirectedAcyclicGraphTest - extends TestCase -{ - //~ Instance fields -------------------------------------------------------- - - private RandomGraphGenerator randomGraphGenerator = null; - private Graph sourceGraph = null; - - //~ Methods ---------------------------------------------------------------- - - @Override protected void setUp() - throws Exception - { - super.setUp(); - - setUpWithSeed(100, 5000, 2); - } - - private void setUpWithSeed(int vertices, int edges, long seed) - { - randomGraphGenerator = - new RepeatableRandomGraphGenerator( - vertices, - edges, - seed); - sourceGraph = - new SimpleDirectedGraph(DefaultEdge.class); - randomGraphGenerator.generateGraph( - sourceGraph, - new LongVertexFactory(), - null); - } - - /** - * Tests the cycle detection capabilities of DirectedAcyclicGraph by - * building a parallel SimpleDirectedGraph and using a CycleDetector to - * check for cycles, and comparing the results. - */ - public void testCycleDetectionInRandomGraphBuild() - { - for (int i = 0; i < 50; i++) { // test with 50 random graph - // configurations - 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.addDagEdge(edgeSource, edgeTarget); - } catch (DirectedAcyclicGraph.CycleFoundException 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 - */ - public void testTopoIterationOrderLinearGraph() - { - DirectedAcyclicGraph dag = - new DirectedAcyclicGraph(DefaultEdge.class); - LinearGraphGenerator graphGen = - new LinearGraphGenerator(100); - graphGen.generateGraph(dag, new LongVertexFactory(), null); - - 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. - */ - public void testTopoIterationOrderComplexGraph() - { - for (int seed = 0; seed < 20; seed++) { - DirectedAcyclicGraph dag = - new DirectedAcyclicGraph(DefaultEdge.class); - RepeatableRandomGraphGenerator graphGen = - new RepeatableRandomGraphGenerator( - 100, - 500, - seed); - graphGen.generateGraph(dag, new LongVertexFactory(), null); - - 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); - } - } - } - - public void testIterationBehaviors() - { - int vertexCount = 100; - - DirectedAcyclicGraph dag = - new DirectedAcyclicGraph(DefaultEdge.class); - RepeatableRandomGraphGenerator graphGen = - new RepeatableRandomGraphGenerator( - vertexCount, - 500, - 2); - graphGen.generateGraph(dag, new LongVertexFactory(), null); - - 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 - } - - // TODO: further iterator tests - } - - // Performance tests have underscores in the names so that they - // they are only run explicitly (not automatically as part of - // default JUnit runs). - - /** - * 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. - */ - public void _testPerformanceVersusStaticChecking() - { - 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) - { - long dynamicDagTime = 0; - long staticDagTime = 0; - - for (int seed = 0; seed < trialsPerConfiguration; seed++) { // test with random graph configurations - setUpWithSeed( - numVertices, - numVertices * connectednessFactor, - seed); - - DirectedAcyclicGraph dag = - new DirectedAcyclicGraph( - DefaultEdge.class); - - long dynamicOpStart = System.nanoTime(); - - for (Long vertex : sourceGraph.vertexSet()) { - dag.addVertex(vertex); - } - - for (DefaultEdge edge : sourceGraph.edgeSet()) { - Long edgeSource = sourceGraph.getEdgeSource(edge); - Long edgeTarget = sourceGraph.getEdgeTarget(edge); - - dag.addEdge(edgeSource, edgeTarget); - } - - dynamicDagTime += System.nanoTime() - dynamicOpStart; - - SimpleDirectedGraph compareGraph = - new SimpleDirectedGraph( - DefaultEdge.class); - - long staticOpStart = System.nanoTime(); - - 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); - } - } - - staticDagTime += System.nanoTime() - staticOpStart; - } - - System.out.println( - "vertices = " + numVertices + " connectednessFactor = " - + connectednessFactor + " trialsPerConfiguration = " - + trialsPerConfiguration); - System.out.println( - "total static DAG time = " + staticDagTime + " ns"); - System.out.println( - "total dynamic DAG time = " + dynamicDagTime + " ns"); - System.out.println(); - } - } - } - - /** - * 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. - */ - public void _testVisitedImplementationPerformance() - { - 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) - { - long arrayDagTime = 0; - long arrayListDagTime = 0; - long hashSetDagTime = 0; - long bitSetDagTime = 0; - - for (int seed = 0; seed < trialsPerConfiguration; seed++) { // test with random graph configurations - setUpWithSeed( - numVertices, - numVertices * connectednessFactor, - seed); - - DirectedAcyclicGraph arrayDag = - new DirectedAcyclicGraph( - DefaultEdge.class, - new DirectedAcyclicGraph.VisitedArrayImpl(), - null); - DirectedAcyclicGraph arrayListDag = - new DirectedAcyclicGraph( - DefaultEdge.class, - new DirectedAcyclicGraph.VisitedArrayListImpl(), - null); - DirectedAcyclicGraph hashSetDag = - new DirectedAcyclicGraph( - DefaultEdge.class, - new DirectedAcyclicGraph.VisitedHashSetImpl(), - null); - DirectedAcyclicGraph bitSetDag = - new DirectedAcyclicGraph( - DefaultEdge.class, - new DirectedAcyclicGraph.VisitedBitSetImpl(), - null); - - long arrayStart = System.nanoTime(); - - for (Long vertex : sourceGraph.vertexSet()) { - arrayDag.addVertex(vertex); - } - - for (DefaultEdge edge : sourceGraph.edgeSet()) { - Long edgeSource = sourceGraph.getEdgeSource(edge); - Long edgeTarget = sourceGraph.getEdgeTarget(edge); - - try { - arrayDag.addDagEdge(edgeSource, edgeTarget); - } catch (DirectedAcyclicGraph.CycleFoundException e) { - // okay - } - } - - arrayDagTime += System.nanoTime() - arrayStart; - - long arrayListStart = System.nanoTime(); - - for (Long vertex : sourceGraph.vertexSet()) { - arrayListDag.addVertex(vertex); - } - - for (DefaultEdge edge : sourceGraph.edgeSet()) { - Long edgeSource = sourceGraph.getEdgeSource(edge); - Long edgeTarget = sourceGraph.getEdgeTarget(edge); - - try { - arrayListDag.addDagEdge(edgeSource, edgeTarget); - } catch (DirectedAcyclicGraph.CycleFoundException e) { - // okay - } - } - - arrayListDagTime += System.nanoTime() - arrayListStart; - - long hashSetStart = System.nanoTime(); - - for (Long vertex : sourceGraph.vertexSet()) { - hashSetDag.addVertex(vertex); - } - - for (DefaultEdge edge : sourceGraph.edgeSet()) { - Long edgeSource = sourceGraph.getEdgeSource(edge); - Long edgeTarget = sourceGraph.getEdgeTarget(edge); - - try { - hashSetDag.addDagEdge(edgeSource, edgeTarget); - } catch (DirectedAcyclicGraph.CycleFoundException e) { - // okay - } - } - - hashSetDagTime += System.nanoTime() - hashSetStart; - - long bitSetStart = System.nanoTime(); - - for (Long vertex : sourceGraph.vertexSet()) { - bitSetDag.addVertex(vertex); - } - - for (DefaultEdge edge : sourceGraph.edgeSet()) { - Long edgeSource = sourceGraph.getEdgeSource(edge); - Long edgeTarget = sourceGraph.getEdgeTarget(edge); - - try { - bitSetDag.addDagEdge(edgeSource, edgeTarget); - } catch (DirectedAcyclicGraph.CycleFoundException e) { - // okay - } - } - - bitSetDagTime += System.nanoTime() - bitSetStart; - } - - System.out.println( - "vertices = " + numVertices + " connectednessFactor = " - + connectednessFactor + " trialsPerConfiguration = " - + trialsPerConfiguration); - System.out.println( - "total array time = " + arrayDagTime + " ns"); - System.out.println( - "total ArrayList time = " + arrayListDagTime + " ns"); - System.out.println( - "total HashSet time = " + hashSetDagTime + " ns"); - System.out.println( - "total BitSet time = " + bitSetDagTime + " ns"); - System.out.println(); - } - } - } - - //~ Inner Classes ---------------------------------------------------------- - - private static class LongVertexFactory - implements VertexFactory - { - private long nextVertex = 0; - - public Long createVertex() - { - return nextVertex++; - } - } - - // it is nice for tests to be easily repeatable, so we use a graph generator - // that we can seed for specific configurations - private static class RepeatableRandomGraphGenerator - extends RandomGraphGenerator - { - public RepeatableRandomGraphGenerator( - int vertices, - int edges, - long seed) - { - super(vertices, edges); - randomizer = new Random(seed); - } - - @Override public void generateGraph( - Graph graph, - VertexFactory vertexFactory, - Map namedVerticesMap) - { - List vertices = new ArrayList(numOfVertexes); - Set edgeGeneratorIds = new HashSet(); - - for (int i = 0; i < numOfVertexes; i++) { - V vertex = vertexFactory.createVertex(); - vertices.add(vertex); - graph.addVertex(vertex); - } - - 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 - } - } - } - } -} - -// End DirectedAcyclicGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/equivalence/EquivalenceGroupCreatorTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/equivalence/EquivalenceGroupCreatorTest.java deleted file mode 100644 index f1c5f7fb9f5..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/equivalence/EquivalenceGroupCreatorTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EquivalenceGroupCreatorTest.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.equivalence; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.experimental.isomorphism.comparators.*; - - -/** - * @author Assaf - * @since Jul 22, 2005 - */ -public class EquivalenceGroupCreatorTest - extends TestCase -{ - //~ Instance fields -------------------------------------------------------- - - // create the groups array as 0 to X (it) - final int INTEGER_ARRAY_SIZE = 25; - - //~ Methods ---------------------------------------------------------------- - - /* - * @see TestCase#setUp() - */ - protected void setUp() - throws Exception - { - super.setUp(); - } - - public void testUniformGroup() - { - // expecting two seperate groups , one with odd , one with even nubmers" - testOneComparator(new UniformEquivalenceComparator(), 1); - - // " expecting 3 seperate groups , one for each mod3 - testOneComparator( - new org.jgrapht.experimental.isomorphism.comparators.Mod3GroupComparator(), - 3); - } - - public void testOddEvenGroup() - { - // " expecting two seperate groups , one with odd , one with even - // nubmers"); - testOneComparator( - new org.jgrapht.experimental.isomorphism.comparators.OddEvenGroupComparator(), - 2); - - // " expecting 3 seperate groups , one for each mod3"); - testOneComparator( - new org.jgrapht.experimental.isomorphism.comparators.Mod3GroupComparator(), - 3); - } - - /** - * Using a chain of evenOdd(mod2) and mod3 comparator , should yield the 6 - * groups , which are infact mod6 , examples: - *
  • mod2 = 0 , mod3 = 0 --> mod6=0 , like : 6 , 12 - *
  • mod2 = 1 , mod3 = 0 --> mod6=1 , like : 7 , 13 - *
  • - */ - public void testComparatorChain() - { - EquivalenceComparatorChain comparatorChain = - new EquivalenceComparatorChainBase( - new OddEvenGroupComparator()); - comparatorChain.appendComparator(new Mod3GroupComparator()); - - // for (int i=0 ; i integerArray = - new ArrayList(INTEGER_ARRAY_SIZE); - for (int i = 0; i < INTEGER_ARRAY_SIZE; i++) { - integerArray.add(i); - } - - EquivalenceSet [] eqGroupArray = - EquivalenceSetCreator.createEqualityGroupOrderedArray( - integerArray, - comparator, - null); - assertEquals(expectedNumOfGroups, eqGroupArray.length); - - // assert the group order size is sorted. - for (int i = 1; i < eqGroupArray.length; i++) { - EquivalenceSet set = eqGroupArray[i]; - assertTrue(set.size() >= eqGroupArray[i - 1].size()); - } - // System.out.println("\nTesting the EquivalenceSet[] returned from - // Integer[" - // +INTEGER_ARRAY_SIZE+"] filled with the integers as the indexes. \n" - // + expectedResult); - // System.out.println("result size="+eqGroupArray.length); - // for (int i = 0; i < eqGroupArray.length; i++) { - // System.out.println(eqGroupArray[i]); - // } - } -} - -// End EquivalenceGroupCreatorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/EdgeTopologyCompare.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/EdgeTopologyCompare.java deleted file mode 100644 index ccf05b40ace..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/EdgeTopologyCompare.java +++ /dev/null @@ -1,70 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * EdgeTopologyCompare.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import org.jgrapht.*; - - -/** - * @author Assaf - * @since Aug 6, 2005 - */ -public class EdgeTopologyCompare -{ - //~ Methods ---------------------------------------------------------------- - - /** - * 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 - */ - @SuppressWarnings("unchecked") - public static boolean compare(Graph g1, Graph g2) - { - boolean result = false; - GraphOrdering lg1 = new GraphOrdering(g1); - GraphOrdering lg2 = new GraphOrdering(g2); - result = lg1.equalsByEdgeOrder(lg2); - - return result; - } -} - -// End EdgeTopologyCompare.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IntegerVertexFactory.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IntegerVertexFactory.java deleted file mode 100644 index a376c6d4624..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IntegerVertexFactory.java +++ /dev/null @@ -1,87 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * IntegerVertexFactory.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import org.jgrapht.*; - - -/** - * Implements createVertex() by producing a sequence of Integers; their values - * start with the successor to the constructor value. - * - *

    for example : IntegerVertexFactory(10); the first createVertex() will - * return Integer=11 - * - * @author Assaf - * @since May 25, 2005 - */ -public class IntegerVertexFactory - implements VertexFactory -{ - //~ Instance fields -------------------------------------------------------- - - private int counter; - - //~ Constructors ----------------------------------------------------------- - - /** - * Equivalent to IntegerVertexFactory(0); - * - * @author Assaf - * @since Aug 6, 2005 - */ - public IntegerVertexFactory() - { - this(0); - } - - public IntegerVertexFactory(int oneBeforeFirstValue) - { - this.counter = oneBeforeFirstValue; - } - - //~ Methods ---------------------------------------------------------------- - - public Integer createVertex() - { - this.counter++; - return new Integer(this.counter); - } -} - -// End IntegerVertexFactory.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IsomorphismInspectorTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IsomorphismInspectorTest.java deleted file mode 100644 index eb99f9acf8d..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/IsomorphismInspectorTest.java +++ /dev/null @@ -1,640 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * IsomorphismInspectorTest.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.experimental.isomorphism.comparators.*; -import org.jgrapht.generate.*; -import org.jgrapht.graph.*; - - -/** - * @author Assaf - * @since May 27, 2005 - */ -public class IsomorphismInspectorTest - extends TestCase -{ - //~ Constructors ----------------------------------------------------------- - - /** - * Constructor for IsomorphismInspectorTest. - * - * @param arg0 - */ - public IsomorphismInspectorTest(String arg0) - { - super(arg0); - } - - //~ Methods ---------------------------------------------------------------- - - /* - * @see TestCase#setUp() - */ - protected void setUp() - throws Exception - { - super.setUp(); - } - - /** - * Calls the same method with different (default) parameters - * EqualityGroupChecker vertexChecker = null EqualityGroupChecker - * edgeChecker = null - */ - private void assertIsomorphic( - Graph [] graphs, - boolean shouldTheyBeIsomorphic) - { - assertIsomorphic(graphs, shouldTheyBeIsomorphic, null, null); - } - - @SuppressWarnings("unchecked") - private void assertIsomorphic( - Graph [] graphs, - boolean shouldTheyBeIsomorphic, - EquivalenceComparator vertexChecker, - EquivalenceComparator edgeChecker) - { - // System.out.println("\nassertIsomorphic:"+shouldTheyBeIsomorphic); - Graph g1 = graphs[0]; - Graph g2 = graphs[1]; - - // System.out.println("g1:"+g1); - // System.out.println("g2:"+g2); - // long beforeTime=System.currentTimeMillis(); - GraphIsomorphismInspector iso = - AdaptiveIsomorphismInspectorFactory.createIsomorphismInspector( - g1, - g2, - vertexChecker, - edgeChecker); - int counter = 0; - while (iso.hasNext()) { - IsomorphismRelation isioResult = (IsomorphismRelation) iso.next(); - - if (false) { - if (counter == 0) { - System.out.println( - "Graphs are isomorphic. Iterating over all options:"); - } - System.out.println(counter + " : " + isioResult); - } - counter++; - } - - // if (counter==0) - // { - // System.out.println("Graphs are NOT isomorphic."); - // } - // long deltaTime=System.currentTimeMillis()-beforeTime; - // String timeDesc; - // timeDesc= deltaTime<=10 ? "<10ms [less than minimum measurement - // time]": String.valueOf(deltaTime); - // System.out.println("# Performence: Isomorphism check in - // MiliSeconds:"+timeDesc); - if (shouldTheyBeIsomorphic) { - assertTrue(counter != 0); - } else { - assertTrue(counter == 0); - } - } - - @SuppressWarnings("unchecked") - private void checkRelation( - Graph [] graphs, - EquivalenceComparator vertexChecker, - EquivalenceComparator edgeChecker) - { - Graph g1 = graphs[0]; - Graph g2 = graphs[1]; - - GraphIsomorphismInspector iso = - AdaptiveIsomorphismInspectorFactory.createIsomorphismInspector( - g1, - g2, - vertexChecker, - edgeChecker); - IsomorphismRelation isoResult; - if (iso.hasNext()) { - isoResult = (IsomorphismRelation) iso.next(); - - Set vertexSet = g1.vertexSet(); - for ( - Iterator iter = vertexSet.iterator(); - iter.hasNext();) - { - Integer v1 = iter.next(); - Integer v2 = isoResult.getVertexCorrespondence(v1, true); - if (false) { - System.out.println("Vertex relation " + v1 + " to " + v2); - } - } - Set edgeSet = g1.edgeSet(); - for ( - Iterator iter = edgeSet.iterator(); - iter.hasNext();) - { - DefaultEdge e1 = iter.next(); - DefaultEdge e2 = isoResult.getEdgeCorrespondence(e1, true); - if (false) { - System.out.println("Vertex relation " + e1 + " to " + e2); - } - } - - // if (counter==0) - // { - // System.out.println("Graphs are isomorphic. Iterating over all - // options:"); - // } - // System.out.println(counter+" : "+isioResult); - - } - } - - @SuppressWarnings("unchecked") - public void testWheelGraphAddRemoveParts() - { - final int NUM_OF_VERTEXES_IN_WHEEL = 6; - final int FIRST_INTEGER_FOR_G2 = 13; - - Graph g1 = - new DefaultDirectedGraph( - DefaultEdge.class); - Graph g2 = - new DefaultDirectedGraph( - DefaultEdge.class); - WheelGraphGenerator gen1 = - new WheelGraphGenerator( - NUM_OF_VERTEXES_IN_WHEEL); - gen1.generateGraph(g1, new IntegerVertexFactory(), null); - - // FIRST_INTEGER_FOR_G2-1 , cause first integer is always the next one. - gen1.generateGraph( - g2, - new IntegerVertexFactory(FIRST_INTEGER_FOR_G2), - null); - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - true); - - // In a wheel , the last vertex is the wheel center! - g1.removeVertex(new Integer(NUM_OF_VERTEXES_IN_WHEEL)); // delete one vertex from g1 - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - false); - - // for example: 10+4 - g2.removeVertex( - new Integer(FIRST_INTEGER_FOR_G2 - + NUM_OF_VERTEXES_IN_WHEEL)); - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - true); - - g1.removeEdge(new Integer(1), new Integer(2)); - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - false); - } - - @SuppressWarnings("unchecked") - public void testLinear4vertexIsomorphicGraph() - { - Graph g1 = - new DefaultDirectedGraph( - DefaultEdge.class); - LinearGraphGenerator gen1 = new LinearGraphGenerator(4); - gen1.generateGraph(g1, new IntegerVertexFactory(), null); - - Graph g2 = - new DefaultDirectedGraph( - DefaultEdge.class); - LinearGraphGenerator gen2 = new LinearGraphGenerator(4); - gen2.generateGraph(g2, new IntegerVertexFactory(5), null); // start vertex from number 6 - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - true); - - checkRelation( - new Graph[] { - g1, - g2 - }, - null, - null); - } - - /** - * Create two graphs which are topologically the same (same number of - * vertexes and same edges connection), but the contents of the vertexes - * belong to different eq. set. g1: 1-->2-->3-->4 g2: 2-->3-->4-->5 g3: - * 3-->4-->5-->6 The eq-group-check is if the number is even or odd. So, g1 - * and g3 are isomorphic. g2 is not isomorphic to either of them. - */ - @SuppressWarnings("unchecked") - public void testLinear4vertexNonIsomorphicCauseOfVertexEqGroup() - { - LinearGraphGenerator gen4 = - new LinearGraphGenerator(4); - - Graph g1 = - new DefaultDirectedGraph( - DefaultEdge.class); - gen4.generateGraph(g1, new IntegerVertexFactory(), null); - - Graph g2 = - new DefaultDirectedGraph( - DefaultEdge.class); - gen4.generateGraph(g2, new IntegerVertexFactory(1), null); // start vertex from number 2 - - Graph g3 = - new DefaultDirectedGraph( - DefaultEdge.class); - gen4.generateGraph(g3, new IntegerVertexFactory(2), null); // start vertex from number 3 - - // first assert all are isomorphic (if vertexChecker is not used) - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - true); - assertIsomorphic( - new Graph[] { - g2, - g3 - }, - true); - assertIsomorphic( - new Graph[] { - g1, - g3 - }, - true); - - // create a functor according to odd even - EquivalenceComparator vertexEqChecker = new OddEvenGroupComparator(); - assertIsomorphic( - new Graph[] { - g1, - g2 - }, - false, - vertexEqChecker, - null); - assertIsomorphic( - new Graph[] { - g2, - g3 - }, - false, - vertexEqChecker, - null); - assertIsomorphic( - new Graph[] { - g1, - g3 - }, - true, - vertexEqChecker, - null); - } - - /** - * Passes an EdgeComparator, which compares according to odd-even edge - * weight. The generated graphs are: A-(12)->B-(10)->C-(5)->D - * A-(10)->B-(18)->C-(3)->D A-(11)->B-(10)->C-(5)->D (the first here is odd) - * - * @author Assaf - * @since Aug 12, 2005 - */ - @SuppressWarnings("unchecked") - public void testEdgeComparator() - { - int LINEAR_GRAPH_VERTEX_NUM = 4; - Graph [] graphsArray = new DirectedGraph[3]; - Character [] charArray = - new Character[] { - new Character('A'), - new Character('B'), - new Character('C'), - new Character('D') - }; - int [][] weigthsArray = - new int[][] { - { - 12, - 10, - 5 - }, - { - 10, - 18, - 3 - }, - { - 11, - 10, - 5 - } - }; - - for (int i = 0; i < graphsArray.length; i++) { - Graph currGraph = - graphsArray[i] = - new DefaultDirectedWeightedGraph( - DefaultWeightedEdge.class); - for (int j = 0; j < LINEAR_GRAPH_VERTEX_NUM; j++) { - currGraph.addVertex(charArray[j]); - } - - // create the 3 edges with weights - for (int j = 0; j < 3; j++) { - Graphs.addEdge( - currGraph, - charArray[j], - charArray[j + 1], - weigthsArray[i][j]); - } - } - - // first assert all are isomorphic (if vertexChecker is not used) - assertIsomorphic(new Graph[] { graphsArray[0], graphsArray[1] }, - true); - assertIsomorphic(new Graph[] { graphsArray[0], graphsArray[2] }, - true); - assertIsomorphic(new Graph[] { graphsArray[1], graphsArray[2] }, - true); - - // create a functor according to odd even - EquivalenceComparator edgeEqChecker = - new DirectedEdgeWeightOddEvenComparator(graphsArray[0]); - assertIsomorphic( - new Graph[] { graphsArray[0], graphsArray[1] }, - true, - null, - edgeEqChecker); - assertIsomorphic( - new Graph[] { graphsArray[0], graphsArray[2] }, - false, - null, - edgeEqChecker); - assertIsomorphic( - new Graph[] { graphsArray[1], graphsArray[2] }, - false, - null, - edgeEqChecker); - } - - @SuppressWarnings("unchecked") - private void assertIsomorphicStopAfterFirstMatch( - Graph [] graphs, - boolean assertActive, - boolean shouldTheyBeIsomorphic, - EquivalenceComparator vertexChecker, - EquivalenceComparator edgeChecker) - { - if (assertActive) { - System.out.println("\nassertIsomorphic:" - + shouldTheyBeIsomorphic); - } - Graph g1 = graphs[0]; - Graph g2 = graphs[1]; - System.out.println("g1:" + g1); - System.out.println("g2:" + g2); - long beforeTime = System.currentTimeMillis(); - GraphIsomorphismInspector iso = - AdaptiveIsomorphismInspectorFactory.createIsomorphismInspector( - g1, - g2, - vertexChecker, - edgeChecker); - boolean isoResult = iso.isIsomorphic(); - if (isoResult) { - System.out.println("Graphs are isomorphic. "); - } else { - System.out.println("Graphs are NOT isomorphic."); - } - - long deltaTime = System.currentTimeMillis() - beforeTime; - String timeDesc; - timeDesc = - (deltaTime <= 10) ? "<10ms [less than minumun measurement time]" - : String.valueOf(deltaTime); - System.out.println( - "# Performence: Isomorphism check in MiliSeconds:" + timeDesc); - if (assertActive) { - assertEquals(shouldTheyBeIsomorphic, isoResult); - } - } - - /** - * Performance test with different number of vertex, edges. For each number - * pair, 3 graphs are generated. The first two, using the same generator, - * the third using a different generator. Note: the 1st and 2nd must be - * isomorphic. The 3rd will most likely not be isomorphic , but on special - * occasaions may be, so do not assert it. (example: empty graph, full mesh - * , rare case that they are not real random). NOTE: RENAME TO testXXX to - * make it work. It shows output and not assertions, so it cannot be used by - * automatic tests. - */ - @SuppressWarnings("unchecked") - public void performanceTestOnRandomGraphs() - throws Exception - { - final int [] numOfVertexesArray = - new int[] { - 6, - 6, - 6, - 8, - 8, - 8, - 10, - 10, - 10, - 12, - 14, - 20, - 30, - 99 - }; - final int [] numOfEdgesArray = - new int[] { - 0, - 4, - 12, - 1, - 15, - 54, - 0, - 40, - 90, - 130, - 50, - 79, - 30, - 234 - }; - - // there will be two different generators. The first will be used for - // 1st,2nd graph - // the other for the3rd graph - final int NUM_OF_GENERATORS = 2; - RandomGraphGenerator [] genArray = - new RandomGraphGenerator[NUM_OF_GENERATORS]; - - String [] graphConctereClassesFullName = - new String[] { // "org.jgrapht.graph.SimpleGraph" , - "org.jgrapht.graph.SimpleDirectedGraph", - "org.jgrapht.graph.DefaultDirectedGraph", - // "org.jgrapht.graph.Multigraph", - // "org.jgrapht.graph.Pseudograph" - }; - - // 3 graphs. 1st,2nd must be isomorphic .3rd probably not iso. - final int SIZE_OF_GENERATED_GRAPH_ARRAY = 3; - - // graphsArray rows are different graph types. Columns are few - // instances of that type - Graph [][] graphsArray = - new Graph[graphConctereClassesFullName.length][SIZE_OF_GENERATED_GRAPH_ARRAY]; - - Graph [] currIsoTestArray = new Graph[2]; - for (int testNum = 0; testNum < numOfVertexesArray.length; testNum++) { - // recreate the graphs (empty) - try { - for (int i = 0; i < graphConctereClassesFullName.length; i++) { - for (int j = 0; j < SIZE_OF_GENERATED_GRAPH_ARRAY; j++) { - graphsArray[i][j] = - (Graph) Class.forName( - graphConctereClassesFullName[i]).newInstance(); - } - } - } catch (Exception e) { - throw new Exception("failed to initilized the graphs", e); - } - - // create generators for the new vertex/edges number - for (int i = 0; i < genArray.length; i++) { - genArray[i] = - new RandomGraphGenerator( - numOfVertexesArray[testNum], - numOfEdgesArray[testNum]); - } - - for ( - int graphType = 0; - graphType < graphConctereClassesFullName.length; - graphType++) - { - System.out.println( - "### numOfVertexes= " + numOfVertexesArray[testNum]); - System.out.println( - "### numOfEdges= " + numOfEdgesArray[testNum]); - System.out.println( - "######### Graph Type:" - + graphConctereClassesFullName[graphType]); - System.out.println( - "# # # # # # # # # # # # # # # # # # # # # # # # # # # #"); - - // 1st and 2nd from genArray[0] - genArray[0].generateGraph( - graphsArray[graphType][0], - new IntegerVertexFactory(), - null); - genArray[0].generateGraph( - graphsArray[graphType][1], - new IntegerVertexFactory(), - null); - - // 3rd from genArray[1] - genArray[1].generateGraph( - graphsArray[graphType][2], - new IntegerVertexFactory(), - null); - - // now start testing - currIsoTestArray[0] = graphsArray[graphType][0]; - currIsoTestArray[1] = graphsArray[graphType][1]; - - assertIsomorphicStopAfterFirstMatch( - currIsoTestArray, - true, - true, - null, - null); - - // remember it is not a MUST - it can be true . DEGUG REASON - // ONLY , and with care - currIsoTestArray[1] = graphsArray[graphType][2]; - assertIsomorphicStopAfterFirstMatch( - currIsoTestArray, - false, - false, - null, - null); - } - } - } -} - -// End IsomorphismInspectorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/DirectedEdgeWeightOddEvenComparator.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/DirectedEdgeWeightOddEvenComparator.java deleted file mode 100644 index 87e66d3cc07..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/DirectedEdgeWeightOddEvenComparator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * DirectedEdgeWeightOddEvenComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id: DirectedEdgeWeightOddEvenComparator.java 489 2006-07-02 02:05:47Z - * perfecthash $ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism.comparators; - -import org.jgrapht.*; -import org.jgrapht.experimental.equivalence.*; - - -/** - * eq.set according to the weights of the edges. Uses Graph.getEdgeWeight(Edge) - * (cast to integer) and checks odd/even. - * - * @author Assaf - * @since Aug 12, 2005 - */ -public class DirectedEdgeWeightOddEvenComparator - implements EquivalenceComparator -{ - //~ Instance fields -------------------------------------------------------- - - private final Graph graph; - - //~ Constructors ----------------------------------------------------------- - - public DirectedEdgeWeightOddEvenComparator(Graph graph) - { - this.graph = graph; - } - - //~ Methods ---------------------------------------------------------------- - - /* (non-Javadoc) - * @see - * - * - * - * - * - * org.jgrapht.experimental.equivalence.EquivalenceComparator#equivalenceCompare(java.lang.Object, - * java.lang.Object, java.lang.Object, java.lang.Object) - */ - @SuppressWarnings("unchecked") - public boolean equivalenceCompare( - Object arg1, - Object arg2, - Object context1, - Object context2) - { - int int1 = (int) graph.getEdgeWeight(arg1); - int int2 = (int) graph.getEdgeWeight(arg2); - - boolean result = ((int1 % 2) == (int2 % 2)); - return result; - } - - /* (non-Javadoc) - * @see - * - * - * - * - * - * org.jgrapht.experimental.equivalence.EquivalenceComparator#equivalenceHashcode(java.lang.Object, - * java.lang.Object) - */ - @SuppressWarnings("unchecked") - public int equivalenceHashcode(Object arg1, Object context) - { - int int1 = (int) graph.getEdgeWeight(arg1); - return int1 % 2; - } -} - -// End DirectedEdgeWeightOddEvenComparator.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/Mod3GroupComparator.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/Mod3GroupComparator.java deleted file mode 100644 index 7c50058ecd3..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/Mod3GroupComparator.java +++ /dev/null @@ -1,86 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * Mod3GroupComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism.comparators; - -import org.jgrapht.experimental.equivalence.*; - - -/** - * Comparator which defines three groups of integers, according to mod3 result - *

  • mod3=0 , - *
  • mod3=1 - *
  • mod3=2 Works only on Integers. - * - * @author Assaf - * @since Jul 22, 2005 - */ -public class Mod3GroupComparator - implements EquivalenceComparator -{ - //~ Methods ---------------------------------------------------------------- - - public boolean equivalenceCompare( - Integer arg1, - Integer arg2, - Object context1, - Object context2) - { - int int1 = arg1.intValue(); - int int2 = arg2.intValue(); - - boolean result = ((int1 % 3) == (int2 % 3)); - return result; - } - - /* Each group must have unique values. - * @see - * - * - * - * - * - * org.jgrapht.experimental.equivalence.EquivalenceComparator#equivalenceHashcode(java.lang.Object) - */ - public int equivalenceHashcode(Integer arg1, Object context) - { - int int1 = arg1.intValue(); - return int1 % 3; - } -} - -// End Mod3GroupComparator.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/OddEvenGroupComparator.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/OddEvenGroupComparator.java deleted file mode 100644 index 9fb4bfe40ab..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/isomorphism/comparators/OddEvenGroupComparator.java +++ /dev/null @@ -1,84 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * OddEvenGroupComparator.java - * ----------------- - * (C) Copyright 2005-2008, by Barak Naveh and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.isomorphism.comparators; - -import org.jgrapht.experimental.equivalence.*; - - -/** - * Comparator which defines two groups of integers. Odds (mod2=1) and - * Evens(mod2=0). Works only on Integers. - * - * @author Assaf - * @since Jul 22, 2005 - */ -public class OddEvenGroupComparator - implements EquivalenceComparator -{ - //~ Methods ---------------------------------------------------------------- - - public boolean equivalenceCompare( - Integer arg1, - Integer arg2, - Object context1, - Object context2) - { - int int1 = arg1.intValue(); - int int2 = arg2.intValue(); - - boolean result = ((int1 % 2) == (int2 % 2)); - return result; - } - - /* Odd and even must have unique values. - * @see - * - * - * - * - * - * org.jgrapht.experimental.equivalence.EquivalenceComparator#equivalenceHashcode(java.lang.Object) - */ - public int equivalenceHashcode(Integer arg1, Object context) - { - int int1 = arg1.intValue(); - return int1 % 2; - } -} - -// End OddEvenGroupComparator.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/experimental/permutation/CompoundPermutationIterTest.java b/jgrapht-core/src/test/java/org/jgrapht/experimental/permutation/CompoundPermutationIterTest.java deleted file mode 100644 index 40d3b4600c1..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/experimental/permutation/CompoundPermutationIterTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * CompoundPermutationIterTest.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.experimental.permutation; - -import java.util.*; - -import junit.framework.*; - - -/** - * @author Assaf - * @since May 30, 2005 - */ -public class CompoundPermutationIterTest - extends TestCase -{ - //~ Instance fields -------------------------------------------------------- - - private CompoundPermutationIter complexPerm; - - //~ Methods ---------------------------------------------------------------- - - /** - * Asserts that the number of permutations is the same as getMax. It also - * verifies that the number is the same when using different internal order - * of the permutation components. Note: The prints and timer can be unmarked - * to see performance results and the permutations array themselves. - */ - public void testGetNext() - { - // System.out.println("testing complex perm {1,1,1,2,2,3,4,5} "); - // StopperTimer timer = new StopperTimer(); - // timer.start(); - - this.complexPerm = - new CompoundPermutationIter( - new int[] { - 1, - 1, - 1, - 2, - 2, - 3, - 4, - 5 - }); - int maxPermNum = this.complexPerm.getMax(); - - // System.out.println(Arrays.toString(this.complexPerm.getPermAsArray())); - int counter = 0; - while (this.complexPerm.hasNext()) { - int [] resultArray = this.complexPerm.getNext(); - - if (false) { - System.out.println(Arrays.toString(resultArray)); - } - counter++; - } - - // System.out.println(counter); - assertEquals(maxPermNum, counter); - - // timer.stopAndReport(); - - // timer.start(); - this.complexPerm = - new CompoundPermutationIter( - new int[] { - 5, - 4, - 3, - 2, - 2, - 1, - 1, - 1 - }); - - // System.out.println("testing complex perm {5,4,3,2,2,1,1,1} "); - // System.out.println(Arrays.toString(this.complexPerm.getPermAsArray())); - counter = 0; - while (this.complexPerm.hasNext()) { - int [] resultArray = this.complexPerm.getNext(); - - if (false) { - System.out.println(Arrays.toString(resultArray)); - } - counter++; - } - - // System.out.println(counter); - assertEquals(maxPermNum, counter); - // timer.stopAndReport(); - } -} - -// End CompoundPermutationIterTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/AllGenerateTests.java b/jgrapht-core/src/test/java/org/jgrapht/generate/AllGenerateTests.java deleted file mode 100644 index 27e6ef6bc56..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/generate/AllGenerateTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------- - * AllGenerateTests.java - * --------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * - */ -package org.jgrapht.generate; - -import junit.framework.*; - - -/** - * A TestSuite for all tests in this package. - * - * @author Barak Naveh - */ -public final class AllGenerateTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllGenerateTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite for all tests in this package. - * - * @return a test suite for all tests in this package. - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // $JUnit-BEGIN$ - suite.addTest(new TestSuite(GraphGeneratorTest.class)); - suite.addTestSuite(RandomGraphGeneratorTest.class); - - // $JUnit-END$ - return suite; - } -} - -// End AllGenerateTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertForestGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertForestGeneratorTest.java new file mode 100644 index 00000000000..6d5bc802d53 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertForestGeneratorTest.java @@ -0,0 +1,164 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link BarabasiAlbertForestGenerator}. + * + * @author Alexandru Valeanu + */ +public class BarabasiAlbertForestGeneratorTest +{ + + @Test + public void testBadParameters() + { + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertForestGenerator<>(0, 10, 100), "Bad parameter"); + + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertForestGenerator<>(-1, 10, 100), "Bad parameter"); + + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertForestGenerator<>(10, 9, 100),"Bad parameter"); + } + + @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 + public void testDirected() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testDirectedWithOneInitialNode() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testUndirectedWithGraphWhichAlreadyHasSomeVertices() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 numTests = 10_000; + + for (int test = 0; test < numTests; 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()); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertGraphGeneratorTest.java new file mode 100644 index 00000000000..18458b5892b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/BarabasiAlbertGraphGeneratorTest.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link BarabasiAlbertGraphGenerator}. + * + * @author Dimitrios Michail + */ +public class BarabasiAlbertGraphGeneratorTest +{ + @Test + public void testBadParameters() + { + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertGraphGenerator<>(0, 10, 100), "Bad parameter"); + + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertGraphGenerator<>(2, 0, 100), "Bad parameter"); + + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertGraphGenerator<>(2, 3, 100), "Bad parameter"); + + assertThrows(IllegalArgumentException.class, () -> new BarabasiAlbertGraphGenerator<>(3, 2, 2), "Bad parameter"); + } + + @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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/ComplementGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/ComplementGraphGeneratorTest.java new file mode 100644 index 00000000000..d505a9168ef --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/ComplementGraphGeneratorTest.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/DirectedScaleFreeGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/DirectedScaleFreeGraphGeneratorTest.java new file mode 100644 index 00000000000..27afd5c3353 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/DirectedScaleFreeGraphGeneratorTest.java @@ -0,0 +1,151 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests for {@link DirectedScaleFreeGraphGenerator} + * + * @author Amr ALHOSSARY + */ +public class DirectedScaleFreeGraphGeneratorTest +{ + private static final long SEED = 17L; + + @Test + public void testBadParameters() + { + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(-0.5f, 0.33f, 0.5f, 0.5f, 500, 500, SEED), "Bad alpha checking"); + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.33f, -0.5f, 0.5f, 0.5f, 500, 500, SEED), "Bad gamma checking"); + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.66f, 0.66f, 0.5f, 0.5f, 500, 500, SEED), "Bad alpha + gamma checking"); + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, -0.5f, 0.5f, 500, 500, SEED), "Bad deltaIn checking"); + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, -0.5f, 500, 500, SEED), "Bad deltaOut checking"); + assertThrows(IllegalArgumentException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, -1, -1, SEED),"Bad target checking"); + assertThrows(NullPointerException.class, () -> new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 500, 500, null), "Bad random number generator checking"); + + } + + @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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GeneralizedPetersenGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GeneralizedPetersenGraphGeneratorTest.java new file mode 100644 index 00000000000..97175029cae --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GeneralizedPetersenGraphGeneratorTest.java @@ -0,0 +1,112 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 testDuererGraphGraph() + { + 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 testMoebiusKantorGraph() + { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomBipartiteGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomBipartiteGraphGeneratorTest.java new file mode 100644 index 00000000000..c0b97ea44d1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomBipartiteGraphGeneratorTest.java @@ -0,0 +1,132 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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) { + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomGraphGeneratorTest.java new file mode 100644 index 00000000000..b8893cc34a4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GnmRandomGraphGeneratorTest.java @@ -0,0 +1,626 @@ +/* + * (C) Copyright 2005-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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); + assertThrows(IllegalArgumentException.class, () -> randomGen.generateGraph(graph)); + } + + /** + * 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; + } + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomBipartiteGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomBipartiteGraphGeneratorTest.java new file mode 100644 index 00000000000..a52642856f7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomBipartiteGraphGeneratorTest.java @@ -0,0 +1,172 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomGraphGeneratorTest.java new file mode 100644 index 00000000000..ae53690e344 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GnpRandomGraphGeneratorTest.java @@ -0,0 +1,244 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * . + * + * @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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/GraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/GraphGeneratorTest.java index 4e9a32ac810..73d2b5d5057 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/generate/GraphGeneratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/GraphGeneratorTest.java @@ -1,92 +1,56 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------- - * GraphGeneratorTest.java - * ----------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 17-Sep-2003 : Initial revision (JVS); - * 07-May-2006 : Changed from List to Set (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; - -import junit.framework.*; - import org.jgrapht.*; -import org.jgrapht.alg.*; +import org.jgrapht.alg.connectivity.*; import org.jgrapht.graph.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; /** * . * * @author John V. Sichi - * @since Sep 17, 2003 */ public class GraphGeneratorTest - extends TestCase { - //~ Static fields/initializers --------------------------------------------- + // ~ Static fields/initializers --------------------------------------------- private static final int SIZE = 10; - //~ Instance fields -------------------------------------------------------- - - private VertexFactory vertexFactory = - new VertexFactory() { - private int i; - - public Object createVertex() - { - return new Integer(++i); - } - }; - - //~ Methods ---------------------------------------------------------------- + // ~ Methods ---------------------------------------------------------------- /** * . */ + @Test public void testEmptyGraphGenerator() { - GraphGenerator gen = - new EmptyGraphGenerator(SIZE); - DirectedGraph g = - new DefaultDirectedGraph(DefaultEdge.class); - Map resultMap = new HashMap(); - gen.generateGraph(g, vertexFactory, resultMap); + 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()); @@ -95,24 +59,21 @@ public void testEmptyGraphGenerator() /** * . */ + @Test public void testLinearGraphGenerator() { - GraphGenerator gen = - new LinearGraphGenerator(SIZE); - DirectedGraph g = - new DefaultDirectedGraph(DefaultEdge.class); - Map resultMap = new HashMap(); - gen.generateGraph(g, vertexFactory, resultMap); + 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); - Iterator vertexIter = g.vertexSet().iterator(); - - while (vertexIter.hasNext()) { - Object vertex = vertexIter.next(); + for (Object vertex : g.vertexSet()) { if (vertex == startVertex) { assertEquals(0, g.inDegreeOf(vertex)); assertEquals(1, g.outDegreeOf(vertex)); @@ -135,14 +96,14 @@ public void testLinearGraphGenerator() /** * . */ + @Test public void testRingGraphGenerator() { - GraphGenerator gen = - new RingGraphGenerator(SIZE); - DirectedGraph g = - new DefaultDirectedGraph(DefaultEdge.class); - Map resultMap = new HashMap(); - gen.generateGraph(g, vertexFactory, resultMap); + 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()); @@ -150,15 +111,14 @@ public void testRingGraphGenerator() assertEquals(1, g.outDegreeOf(startVertex)); Object nextVertex = startVertex; - Set seen = new HashSet(); + Set seen = new HashSet<>(); for (int i = 0; i < SIZE; ++i) { - DefaultEdge nextEdge = - g.outgoingEdgesOf(nextVertex).iterator().next(); + DefaultEdge nextEdge = g.outgoingEdgesOf(nextVertex).iterator().next(); nextVertex = g.getEdgeTarget(nextEdge); assertEquals(1, g.inDegreeOf(nextVertex)); assertEquals(1, g.outDegreeOf(nextVertex)); - assertTrue(!seen.contains(nextVertex)); + assertFalse(seen.contains(nextVertex)); seen.add(nextVertex); } @@ -170,16 +130,42 @@ public void testRingGraphGenerator() /** * . */ + @Test public void testCompleteGraphGenerator() { - Graph completeGraph = - new SimpleGraph(DefaultEdge.class); + 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(10); - completeGenerator.generateGraph( - completeGraph, - new ClassBasedVertexFactory(Object.class), - null); + new CompleteGraphGenerator<>(); + completeGenerator.generateGraph(completeGraph); // complete graph with 10 vertices has 10*(10-1)/2 = 45 edges assertEquals(45, completeGraph.edgeSet().size()); @@ -188,55 +174,68 @@ public void testCompleteGraphGenerator() /** * . */ + @Test public void testScaleFreeGraphGenerator() { - DirectedGraph graph = - new DefaultDirectedGraph(DefaultEdge.class); - ScaleFreeGraphGenerator generator = - new ScaleFreeGraphGenerator(500); - generator.generateGraph(graph, vertexFactory, null); - ConnectivityInspector inspector = - new ConnectivityInspector(graph); - assertTrue( - "generated graph is not connected", - inspector.isGraphConnected()); + 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(inspector.isConnected(), "generated graph is not connected"); try { - generator = new ScaleFreeGraphGenerator(-50); + new ScaleFreeGraphGenerator<>(-50); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { } try { - generator = - new ScaleFreeGraphGenerator(-50, 31337); + new ScaleFreeGraphGenerator<>(-50, 31337); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { } - generator = new ScaleFreeGraphGenerator(0); - DirectedGraph empty = - new DefaultDirectedGraph(DefaultEdge.class); - generator.generateGraph(empty, vertexFactory, null); - assertTrue("non-empty graph generated", empty.vertexSet().size() == 0); + generator = new ScaleFreeGraphGenerator<>(0); + Graph empty = new DefaultDirectedGraph<>( + SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); + generator.generateGraph(empty); + assertTrue(empty.vertexSet().isEmpty(), "non-empty graph generated"); } /** * . */ + @Test public void testCompleteBipartiteGraphGenerator() { - Graph completeBipartiteGraph = - new SimpleGraph( - DefaultEdge.class); + 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( - 10, - 4); - completeBipartiteGenerator.generateGraph( - completeBipartiteGraph, - new ClassBasedVertexFactory(Object.class), - null); + 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 @@ -247,18 +246,14 @@ public void testCompleteBipartiteGraphGenerator() /** * . */ + @Test public void testHyperCubeGraphGenerator() { - Graph hyperCubeGraph = - new SimpleGraph( - DefaultEdge.class); + Graph hyperCubeGraph = new SimpleGraph<>( + SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); HyperCubeGraphGenerator hyperCubeGenerator = - new HyperCubeGraphGenerator( - 4); - hyperCubeGenerator.generateGraph( - hyperCubeGraph, - new ClassBasedVertexFactory(Object.class), - null); + new HyperCubeGraphGenerator<>(4); + hyperCubeGenerator.generateGraph(hyperCubeGraph); // Hypercube of 4 dimensions should have 2^4=16 vertices and // 4*2^(4-1)=32 total edges @@ -269,19 +264,14 @@ public void testHyperCubeGraphGenerator() /** * . */ + @Test public void testStarGraphGenerator() { - Map map = new HashMap(); - Graph starGraph = - new SimpleGraph( - DefaultEdge.class); - StarGraphGenerator starGenerator = - new StarGraphGenerator( - 10); - starGenerator.generateGraph( - starGraph, - new ClassBasedVertexFactory(Object.class), - map); + 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()); @@ -292,99 +282,57 @@ public void testStarGraphGenerator() /** * . */ + @Test public void testGridGraphGenerator() { int rows = 3; int cols = 4; - //the form of these two classes helps debugging - class StringVertexFactory - implements VertexFactory - { - int index = 1; - - @Override public String createVertex() - { - return String.valueOf(index++); - } - } - - class StringEdgeFactory - implements EdgeFactory - { - @Override public String createEdge( - String sourceVertex, - String targetVertex) - { - return new String(sourceVertex + '-' + targetVertex); - } - } + GridGraphGenerator generator = new GridGraphGenerator<>(rows, cols); + Map resultMap = new HashMap<>(); - GridGraphGenerator generator = - new GridGraphGenerator(rows, cols); - Map resultMap = new HashMap(); - - //validating a directed and undirected graph - Graph directedGridGraph = - new DefaultDirectedGraph(new StringEdgeFactory()); - generator.generateGraph( - directedGridGraph, - new StringVertexFactory(), - resultMap); + // 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(new StringEdgeFactory()); - generator.generateGraph( - undirectedGridGraph, - new StringVertexFactory(), - resultMap); + 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) + 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 instanceof UndirectedGraph) ? 1 : 2); + expectedVerticeNum, gridGraph.vertexSet().size(), + "number of vertices is wrong (" + gridGraph.vertexSet().size() + "), should be " + + expectedVerticeNum); + 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; + expectedEdgesNum, gridGraph.edgeSet().size(), + "number of edges is wrong (" + gridGraph.edgeSet().size() + "), should be " + + expectedEdgesNum); + + int cornerVertices = 0, borderVertices = 0, innerVertices = 0, neighborsSize; int expCornerVertices = 4; - int expBorderVertices = - Math.max(((rows - 2) * 2) + ((cols - 2) * 2), 0); + int expBorderVertices = Math.max(((rows - 2) * 2) + ((cols - 2) * 2), 0); int expInnerVertices = Math.max((rows - 2) * (cols - 2), 0); - Set neighbors = new HashSet(); + 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)); + (neighborsSize == 2) || (neighborsSize == 3) || (neighborsSize == 4), + "vertex with illegal number of neighbors (" + neighborsSize + ")."); if (neighborsSize == 2) { cornerVertices++; } else if (neighborsSize == 3) { @@ -394,42 +342,31 @@ public void validateGridGraphGenerator( } } assertEquals( + expCornerVertices, cornerVertices, "there should be exactly " + expCornerVertices - + " corner (with two neighbors) vertices. " - + " actual number is " + cornerVertices + ".", - expCornerVertices, - cornerVertices); + + " corner (with two neighbors) vertices. " + " actual number is " + cornerVertices + + "."); assertEquals( - "there should be exactly " + expBorderVertices - + " border (with three neighbors) vertices. " - + " actual number is " + borderVertices + ".", - expBorderVertices, - borderVertices); + expBorderVertices, borderVertices, + "there should be exactly " + expBorderVertices + + " border (with three neighbors) vertices. " + " actual number is " + + borderVertices + "."); assertEquals( + expInnerVertices, innerVertices, "there should be exactly " + expInnerVertices - + " inner (with four neighbors) vertices. " - + " actual number is " + innerVertices + ".", - expInnerVertices, - innerVertices); + + " inner (with four neighbors) vertices. " + " actual number is " + innerVertices + + "."); // result map validations Set keys = resultMap.keySet(); assertEquals( - "result map contains should contains exactly 4 corner verices", - 4, - keys.size()); + 4, keys.size(), "result map contains should contains exactly 4 corner verices"); for (String key : keys) { neighbors.clear(); - neighbors.addAll( - Graphs.neighborListOf(gridGraph, resultMap.get(key))); + neighbors.addAll(Graphs.neighborListOf(gridGraph, resultMap.get(key))); neighborsSize = neighbors.size(); - assertEquals( - "corner vertex should have exactly 2 neighbors", - 2, - neighborsSize); + assertEquals(2, neighborsSize, "corner vertex should have exactly 2 neighbors"); } } } - -// End GraphGeneratorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/KleinbergSmallWorldGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/KleinbergSmallWorldGraphGeneratorTest.java new file mode 100644 index 00000000000..69b615358f8 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/KleinbergSmallWorldGraphGeneratorTest.java @@ -0,0 +1,109 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/LinearizedChordDiagramGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/LinearizedChordDiagramGraphGeneratorTest.java new file mode 100644 index 00000000000..03d97dd7bfe --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/LinearizedChordDiagramGraphGeneratorTest.java @@ -0,0 +1,158 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testMultiGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testSimpleGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testDirectedMultiGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testDirectedSimpleGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/NamedGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/NamedGraphGeneratorTest.java new file mode 100644 index 00000000000..e8dc80867b6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/NamedGraphGeneratorTest.java @@ -0,0 +1,357 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 testGroetzschGraph() + { + 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 testSchlaefliGraph() + { + 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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/PlantedPartitionGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/PlantedPartitionGraphGeneratorTest.java new file mode 100644 index 00000000000..4197163cc2b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/PlantedPartitionGraphGeneratorTest.java @@ -0,0 +1,357 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Emilio Cruciani + */ +public class PlantedPartitionGraphGeneratorTest +{ + private static final long SEED = 5; + + /* bad inputs */ + + @Test + public void testNegativeL() + { + assertThrows(IllegalArgumentException.class, () -> new PlantedPartitionGraphGenerator<>(-5, 10, 0.5, 0.1)); + } + + @Test + public void testNegativeK() + { + assertThrows(IllegalArgumentException.class, () -> new PlantedPartitionGraphGenerator<>(5, -10, 0.5, 0.1)); + } + + @Test + public void testNegativeP() + { + assertThrows(IllegalArgumentException.class, () -> new PlantedPartitionGraphGenerator<>(5, 10, -0.5, 0.1)); + } + + @Test + public void testNegativeQ() + { + assertThrows(IllegalArgumentException.class, () -> new PlantedPartitionGraphGenerator<>(5, 10, 0.5, -0.1)); + } + + @Test + public void testTooLargeP() + { + assertThrows(IllegalArgumentException.class, () -> new PlantedPartitionGraphGenerator<>(5, 10, 1.5, 0.1)); + } + + @Test + public void testTooLargeQ() + { + assertThrows(IllegalArgumentException.class, () -> 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); + assertThrows(IllegalArgumentException.class, () -> gen.generateGraph(g)); + } + + /* 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); + + assertThrows(IllegalStateException.class, () -> gen.getCommunities()); + } + + @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); + + assertThrows(IllegalStateException.class, () -> gen.generateGraph(f)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/PruferTreeGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/PruferTreeGeneratorTest.java new file mode 100644 index 00000000000..4db723f5720 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/PruferTreeGeneratorTest.java @@ -0,0 +1,176 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link PruferTreeGenerator} + * + * @author Alexandru Valeanu + */ +public class PruferTreeGeneratorTest +{ + + @Test + public void testNullPruferSequence() + { + assertThrows(IllegalArgumentException.class, () -> 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); + assertEquals(2, tree.vertexSet().size()); + } + + @Test + public void testInvalidPruferSequence() + { + assertThrows(IllegalArgumentException.class, () -> 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); + + assertEquals(6, tree.vertexSet().size()); + + int[] degrees = tree.vertexSet().stream().mapToInt(tree::degreeOf).toArray(); + Arrays.sort(degrees); + + assertArrayEquals(new int[] { 1, 1, 1, 1, 2, 4 }, degrees); + } + + @Test + public void testZeroVertices() + { + assertThrows(IllegalArgumentException.class, () -> new PruferTreeGenerator<>(0)); + } + + @Test + public void testNullRNG() + { + assertThrows(NullPointerException.class, () -> new PruferTreeGenerator<>(100, null)); + } + + @Test + public void testDirectedGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph tree = new DirectedAcyclicGraph<>( + SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); + + PruferTreeGenerator generator = new PruferTreeGenerator<>(10); + + generator.generateGraph(tree); + }); + } + + @Test + public void testNullGraph() + { + assertThrows(NullPointerException.class, () -> { + 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); + assertTrue(GraphTests.isTree(tree)); + } + + @Test + public void testExistingVertices() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 numTests = 500; + + for (int test = 0; test < numTests; 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); + 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); + assertTrue(GraphTests.isTree(tree)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/RandomGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/RandomGraphGeneratorTest.java deleted file mode 100644 index 823d43a7cdc..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/generate/RandomGraphGeneratorTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * RandomGraphGeneratorTest.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.generate; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.experimental.isomorphism.*; -import org.jgrapht.graph.*; - - -/** - * @author Assaf - * @since Aug 6, 2005 - */ -public class RandomGraphGeneratorTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - public void testGenerateDirectedGraph() - { - List> graphArray = - new ArrayList>(); - for (int i = 0; i < 3; ++i) { - graphArray.add( - new SimpleDirectedGraph( - DefaultEdge.class)); - } - - generateGraphs(graphArray, 11, 100); - - assertTrue( - EdgeTopologyCompare.compare(graphArray.get(0), graphArray.get(1))); - // cannot assert false , cause it may be true once in a while (random) - // but it generally should work. - // assertFalse(EdgeTopologyCompare.compare(graphArray.get(1),graphArray.get(2))); - } - - public void testGenerateListenableUndirectedGraph() - { - List> graphArray = - new ArrayList>(); - for (int i = 0; i < 3; ++i) { - graphArray.add( - new ListenableUndirectedGraph( - DefaultEdge.class)); - } - - generateGraphs(graphArray, 11, 50); - - assertTrue( - EdgeTopologyCompare.compare(graphArray.get(0), graphArray.get(1))); - } - - public void testBadVertexFactory() - { - RandomGraphGenerator randomGen = - new RandomGraphGenerator( - 10, - 3); - Graph graph = - new SimpleDirectedGraph(DefaultEdge.class); - try { - randomGen.generateGraph( - graph, - new ClassBasedVertexFactory(String.class), - null); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException ex) { - // expected - } - } - - /** - * Generates 3 graphs with the same numOfVertex and numOfEdges. The first - * two are generated using the same RandomGraphGenerator; the third is - * 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) - { - RandomGraphGenerator randomGen = - new RandomGraphGenerator( - numOfVertex, - numOfEdges); - - randomGen.generateGraph( - graphs.get(0), - new IntegerVertexFactory(), - null); - - // use the same randomGen - randomGen.generateGraph( - graphs.get(1), - new IntegerVertexFactory(), - null); - - // use new randomGen here - RandomGraphGenerator newRandomGen = - new RandomGraphGenerator( - numOfVertex, - numOfEdges); - - newRandomGen.generateGraph( - graphs.get(2), - new IntegerVertexFactory(), - null); - } -} - -// End RandomGraphGeneratorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/RandomRegularGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/RandomRegularGraphGeneratorTest.java new file mode 100644 index 00000000000..15ce0e11a30 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/RandomRegularGraphGeneratorTest.java @@ -0,0 +1,159 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link RandomRegularGraphGenerator}. + * + * @author Emilio Cruciani + */ +public class RandomRegularGraphGeneratorTest +{ + private static final long SEED = 5; + + @Test + public void testNegativeN() + { + assertThrows(IllegalArgumentException.class, () -> new RandomRegularGraphGenerator<>(-10, 1)); + } + + @Test + public void testNegativeD() + { + assertThrows(IllegalArgumentException.class, () -> new RandomRegularGraphGenerator<>(10, -1)); + } + + @Test + public void testDGreaterThanN() + { + assertThrows(IllegalArgumentException.class, () -> new RandomRegularGraphGenerator<>(10, 15)); + } + + @Test + public void testOddDTimesN() + { + assertThrows(IllegalArgumentException.class, () -> new RandomRegularGraphGenerator<>(5, 3)); + } + + @Test + public void testDirectedGraph() + { + GraphGenerator gen = + new RandomRegularGraphGenerator<>(10, 2); + Graph g = new DefaultDirectedGraph<>( + SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); + assertThrows(IllegalArgumentException.class, () -> gen.generateGraph(g)); + } + + @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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/WattsStrogatzGraphGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/WattsStrogatzGraphGeneratorTest.java new file mode 100644 index 00000000000..674520f1b87 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/WattsStrogatzGraphGeneratorTest.java @@ -0,0 +1,201 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Dimitrios Michail + */ +public class WattsStrogatzGraphGeneratorTest +{ + @Test + public void testLessThan3Nodes() + { + assertThrows(IllegalArgumentException.class, () -> 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/WindmillGraphsGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/WindmillGraphsGeneratorTest.java new file mode 100644 index 00000000000..de05d9b9cf2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/WindmillGraphsGeneratorTest.java @@ -0,0 +1,150 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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 + } + + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/DistributorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/DistributorTest.java new file mode 100644 index 00000000000..30263fecb0c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/DistributorTest.java @@ -0,0 +1,207 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testDistributor_NoUpperBounds_NoValidDistribution() + { + assertThrows(IllegalArgumentException.class, () -> { + 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; + } + }); + 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 + public void testDistributor_NoLowerBounds_NoValidDistribution() + { + assertThrows(IllegalArgumentException.class, () -> { + 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; + } + }); + + 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilderTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilderTest.java new file mode 100644 index 00000000000..89f26a94b7e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorConfigBuilderTest.java @@ -0,0 +1,413 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +import static org.jgrapht.generate.netgen.NetworkGenerator.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * 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 + public void testNodeNum_NodeNumNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testNodeNum_NegativeNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setNodeNum(-1).build()); + } + + @Test + public void testNodeNum_TooHighNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setNodeNum(MAX_NODE_NUM + 1).build()); + } + + // -------------------------- arc num tests -------------------------- + + @Test + public void testArcNum_ArcNumNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testArcNum_NegativeArcNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setArcNum(-1).build()); + } + + @Test + public void testArcNum_TooHighArcNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setArcNum(MAX_ARC_NUM + 1).build()); + } + + @Test + public void testArcNum_TooFewArcsInAssignmentProblem_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getAssignmentBuilder().setArcNum(1).build()); + } + + @Test + public void testArcNum_MinimumNumberOfArcsInAssignmentProblem_Ok() + { + getAssignmentBuilder().setArcNum(2).build(); + } + + @Test + public void testArcNum_TooManyArcsInAssignmentProblem_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getAssignmentBuilder().setArcNum(5).build()); + } + + @Test + public void testArcNum_MaximumNumberOfArcsInAssignmentProblem_Ok() + { + getAssignmentBuilder().setArcNum(4).build(); + } + + @Test + public void testArcNum_TooFewArcsInMinCostFlowProblem_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setArcNum(7).build()); + } + + @Test + public void testArcNum_MinimumNumberOfArcsInMinCostFlowProblem_Ok() + { + getMinCostFlowBuilder().setArcNum(8).build(); + } + + @Test + public void testArcNum_TooManyArcsInMinCostFlowProblem_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testSourceNum_SourceNumNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testSourceNum_NegativeSourceNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setSourceNum(-1).build()); + } + + @Test + public void testSourceNum_SourceNumGreaterThanNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setSourceNum(11).build()); + } + + @Test + public void testSourceNum_SinkNumNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testSinkNum_NegativeSinkNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setSinkNum(-1).build()); + } + + @Test + public void testSinkNum_SinkNumGreaterThanNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setSinkNum(11).build()); + } + + @Test + public void testSourceSinkNum_SourceNumPlusSinkNumGreaterThanTheNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setSourceNum(5).setSinkNum(6).build()); + } + + // -------------------------- transshipment source and sinks test -------------------------- + + @Test + public void testTransshipmentSourceNum_NegativeTransshipmentSourceNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTSourceNum(-1).build()); + } + + @Test + public void testTransshipmentSourceNum_TransshipmentSourceNumGreaterThanSourceNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTSourceNum(3).build()); + } + + @Test + public void testTransshipmentSinkNum_NegativeTransshipmentSinkNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTSinkNum(-1).build()); + } + + @Test + public void testTransshipmentSinkNum_TransshipmentSinkNumGreaterThanSourceNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTSinkNum(4).build()); + } + + // -------------------------- supply tests -------------------------- + + @Test + public void testSupply_SupplyNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testSupply_NegativeSupply_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTotalSupply(-1).build()); + } + + @Test + public void testSupply_TooHighSupply_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTotalSupply(MAX_SUPPLY + 1).build()); + } + + @Test + public void testSupply_MaximumSupply_Ok() + { + getMinCostFlowBuilder().setTotalSupply(MAX_SUPPLY).build(); + } + + @Test + public void testSupply_SupplySmallerThanSourceNodeNum_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setTotalSupply(1).build()); + } + + // -------------------------- capacities tests -------------------------- + + @Test + public void testCapacities_NegativeMinimumCapacity_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCap(-1).build()); + } + + @Test + public void testCapacities_NegativeMaximumCapacity_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMaxCap(-1).build()); + } + + @Test + public void testCapacities_MinimumCapacityNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testCapacities_MaximumCapacityNotSet_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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 + public void testCapacities_TooHighMinimumCapacity_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCap(CAPACITY_COST_BOUND + 1)); + } + + @Test + public void testCapacities_MaximumMinimumCapacity_Ok() + { + getMinCostFlowBuilder() + .setMinCap(CAPACITY_COST_BOUND).setMaxCap(CAPACITY_COST_BOUND).build(); + } + + @Test + public void testCapacities_TooHighMaximumCapacity_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMaxCap(CAPACITY_COST_BOUND + 1)); + } + + @Test + public void testCapacities_MaximumMaximumCapacity_Ok() + { + getMinCostFlowBuilder().setMaxCap(CAPACITY_COST_BOUND).build(); + } + + @Test + public void testCapacities_MinimumCapacityGreaterThatMaximumCapacity_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCap(10).setMaxCap(9).build()); + } + + // -------------------------- costs tests -------------------------- + + @Test + public void testCosts_TooLowMinimumCost_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCost(-CAPACITY_COST_BOUND - 1).build()); + } + + @Test + public void testCosts_TooLowMaximumCost_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMaxCost(-CAPACITY_COST_BOUND - 1).build()); + } + + @Test + public void testCosts_TooHighMinimumCost_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCost(CAPACITY_COST_BOUND + 1).build()); + } + + @Test + public void testCosts_TooHighMaximumCost_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMaxCost(CAPACITY_COST_BOUND + 1).build()); + } + + @Test + public void testCosts_MinimumCostGreaterThanMaximumCost_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setMinCost(10).setMaxCost(9).build()); + } + + // -------------------------- percent capacitated tests -------------------------- + + @Test + public void testPercentCapacitated_NegativeValue_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setPercentCapacitated(-1).build()); + } + + @Test + public void testPercentCapacitated_TooHighValue_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setPercentCapacitated(101).build()); + } + + // -------------------------- percent with inf cost tests -------------------------- + + @Test + public void testPercentWithInfCost_NegativeValue_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> getMinCostFlowBuilder().setPercentWithInfCost(-1).build()); + } + + @Test + public void testPercentWithInfCost_TooHighValue_IllegalArgumentException() + { + assertThrows(IllegalArgumentException.class, () -> 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorTest.java new file mode 100644 index 00000000000..82d13b0bc72 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/NetworkGeneratorTest.java @@ -0,0 +1,518 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.Test; + +import java.util.*; +import java.util.function.Function; + +import static org.jgrapht.generate.netgen.NetworkGenerator.MAX_SUPPLY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AllGraphTests.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AllGraphTests.java deleted file mode 100644 index 26f572708c6..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/AllGraphTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * AllGraphTests.java - * ----------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 03-Aug-2003 : Initial revision (BN); - * - */ -package org.jgrapht.graph; - -import junit.framework.*; - - -/** - * A TestSuite for all tests in this package. - * - * @author Barak Naveh - * @since Aug 3, 2003 - */ -public final class AllGraphTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllGraphTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite for all tests in this package. - * - * @return a test suite for all tests in this package. - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // $JUnit-BEGIN$ - suite.addTest(new TestSuite(DefaultDirectedGraphTest.class)); - suite.addTest(new TestSuite(ListenableGraphTest.class)); - suite.addTest(new TestSuite(SimpleDirectedGraphTest.class)); - suite.addTest(new TestSuite(AsUndirectedGraphTest.class)); - suite.addTest(new TestSuite(AsUnweightedGraphTest.class)); - suite.addTest(new TestSuite(CloneTest.class)); - suite.addTest(new TestSuite(SerializationTest.class)); - suite.addTest(new TestSuite(GenericGraphsTest.class)); - - // $JUnit-END$ - return suite; - } -} - -// End AllGraphTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsGraphUnionTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsGraphUnionTest.java new file mode 100644 index 00000000000..553a573e709 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/AsGraphUnionTest.java @@ -0,0 +1,316 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 ---------------------------------------------------------------- + + @BeforeEach + 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsSubgraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsSubgraphTest.java new file mode 100644 index 00000000000..de07f6515ac --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/AsSubgraphTest.java @@ -0,0 +1,336 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsUndirectedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUndirectedGraphTest.java index 1a3a033b8c1..4bf71a483d7 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/AsUndirectedGraphTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUndirectedGraphTest.java @@ -1,48 +1,31 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* -------------------------- - * AsUndirectedGraphTest.java - * -------------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 14-Aug-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * A unit test for the AsDirectedGraph view. @@ -50,62 +33,53 @@ * @author John V. Sichi */ public class AsUndirectedGraphTest - extends EnhancedTestCase { - //~ Instance fields -------------------------------------------------------- + // ~ Instance fields -------------------------------------------------------- - private DirectedGraph directed; + 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 UndirectedGraph undirected; - - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public AsUndirectedGraphTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- + private Graph undirected; /** * . */ + @Test public void testAddEdge() { try { undirected.addEdge(v3, v4); - assertFalse(); + fail(); // should not get here } catch (UnsupportedOperationException e) { - assertTrue(); } assertEquals( - "([v1, v2, v3, v4], [{v1,v2}, {v2,v3}, {v2,v4}, {v4,v4}])", - undirected.toString()); + "([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)); + assertTrue(undirected.containsVertex(v5)); + assertTrue(directed.containsVertex(v5)); } /** * . */ + @Test public void testDegreeOf() { assertEquals(1, undirected.degreeOf(v1)); @@ -117,12 +91,72 @@ public void testDegreeOf() /** * . */ + @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()); + assertEquals(directed.getEdge(v2, v3), edges.iterator().next()); edges = undirected.getAllEdges(v4, v4); assertEquals(1, edges.size()); @@ -132,59 +166,65 @@ public void testGetAllEdges() /** * . */ + @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(v1, v2), undirected.getEdge(v1, v2)); + assertEquals(directed.getEdge(v1, v2), undirected.getEdge(v2, v1)); - assertEquals( - directed.getEdge(v4, v4), - undirected.getEdge(v4, v4)); + 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)); + assertFalse(undirected.containsEdge(loop)); + assertFalse(directed.containsEdge(loop)); } /** * . */ + @Test public void testRemoveVertex() { undirected.removeVertex(v4); - assertEquals(false, undirected.containsVertex(v4)); - assertEquals(false, directed.containsVertex(v4)); + assertFalse(undirected.containsVertex(v4)); + assertFalse(directed.containsVertex(v4)); } /** * . */ - protected void setUp() + @Test + public void testToString() { - directed = - new DefaultDirectedGraph( - DefaultEdge.class); - undirected = new AsUndirectedGraph(directed); + 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()); + } + + /** + * . + */ + @BeforeEach + public void setUp() + { + directed = new DefaultDirectedGraph<>(DefaultEdge.class); + undirected = new AsUndirectedGraph<>(directed); directed.addVertex(v1); directed.addVertex(v2); directed.addVertex(v3); directed.addVertex(v4); - directed.addEdge(v1, v2); - directed.addEdge(v2, v3); - directed.addEdge(v2, v4); + e12 = directed.addEdge(v1, v2); + e23 = directed.addEdge(v2, v3); + e24 = directed.addEdge(v2, v4); loop = directed.addEdge(v4, v4); } } - -// End AsUndirectedGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnmodifiableGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnmodifiableGraphTest.java new file mode 100644 index 00000000000..f0943f42655 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnmodifiableGraphTest.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2024-2024, by Sung Ho Yoon and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 static org.junit.jupiter.api.Assertions.*; + +import org.jgrapht.*; +import org.junit.jupiter.api.*; + +@DisplayName("Unmodifiable graph view tests") +public class AsUnmodifiableGraphTest { + + 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 baseGraph; + private Graph unmodifiableGraph; + + @BeforeEach + public void setUp() + { + this.baseGraph = + new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); + + baseGraph.addVertex(v1); + baseGraph.addVertex(v2); + baseGraph.addVertex(v3); + baseGraph.addVertex(v4); + e12 = Graphs.addEdge(baseGraph, v1, v2, 6d); + e23 = Graphs.addEdge(baseGraph, v2, v3, 456d); + e24 = Graphs.addEdge(baseGraph, v2, v4, 0.587d); + loop = Graphs.addEdge(baseGraph, v4, v4, 6781234453486d); + + this.unmodifiableGraph = new AsUnmodifiableGraph<>(this.baseGraph); + } + + @DisplayName("Test null graph") + @Test + public void testNullGraph() { + assertThrows(NullPointerException.class, () -> new AsUnmodifiableGraph<>(null)); + } + + @DisplayName("Test edge addition") + @Test + public void testAddEdge() + { + assertThrows(UnsupportedOperationException.class, () -> unmodifiableGraph.addEdge(v3, v4)); + assertThrows(UnsupportedOperationException.class, () -> { + DefaultWeightedEdge e34 = new DefaultWeightedEdge(); + e34.source = v3; + e34.target = v4; + e34.weight = 2d; + unmodifiableGraph.addEdge(v3, v4, e34); + }); + } + + @DisplayName("Test vertex addition") + @Test + public void testAddVertex() { + assertThrows(UnsupportedOperationException.class, () -> unmodifiableGraph.addVertex()); + assertThrows(UnsupportedOperationException.class, () -> unmodifiableGraph.addVertex("v5")); + } + + @DisplayName("Test edge weight modification") + @Test + public void testSetEdgeWeight() { + assertThrows(UnsupportedOperationException.class, () -> unmodifiableGraph.setEdgeWeight(v1, v2, 0d)); + assertEquals(6d, baseGraph.getEdgeWeight(e12)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnweightedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnweightedGraphTest.java index fd48520ca81..d24d5619f80 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnweightedGraphTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnweightedGraphTest.java @@ -1,143 +1,113 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * AsUnweightedGraphTest.java - * -------------------------- - * (C) Copyright 2007-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2018-2024, by Lukas Harzenetter and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 22-Sep-2007 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.fail; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; -/** - * A unit test for the AsUnweighted[Directed]Graph views. - * - * @author John V. Sichi - */ public class AsUnweightedGraphTest - extends EnhancedTestCase { - //~ Static fields/initializers --------------------------------------------- - - private static final String v1 = "v1"; - private static final String v2 = "v2"; - private static final String v3 = "v3"; - //~ Constructors ----------------------------------------------------------- + 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; /** - * @see junit.framework.TestCase#TestCase(java.lang.String) + * Similar set up as created by {@link AsUndirectedGraphTest}. */ - public AsUnweightedGraphTest(String name) + @BeforeEach + public void setUp() { - super(name); + 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); } - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testDirected() + @Test + public void getEdgeWeightOfE12() { - DefaultDirectedWeightedGraph directed = - new DefaultDirectedWeightedGraph( - DefaultWeightedEdge.class); - constructWeighted(directed); - - AsUnweightedDirectedGraph unweighted = - new AsUnweightedDirectedGraph( - directed); - checkView(directed, unweighted); + assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e12), 0); } - /** - * . - */ - public void testUndirected() + @Test + public void getEdgeWeightOfE23() { - WeightedGraph undirected = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); - constructWeighted(undirected); + assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e23), 0); + } - AsUnweightedGraph unweighted = - new AsUnweightedGraph( - undirected); - checkView(undirected, unweighted); + @Test + public void getEdgeWeightOfE24() + { + assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e24), 0); } - private void constructWeighted( - WeightedGraph weighted) + @Test + public void getEdgeWeightOfLoop() { - weighted.addVertex(v1); - weighted.addVertex(v2); - weighted.addVertex(v3); - Graphs.addEdge(weighted, v1, v2, 3.0); - assertEquals( - 3.0, - weighted.getEdgeWeight( - weighted.getEdge(v1, v2))); + assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(loop), 0); } - private void checkView( - WeightedGraph weighted, - Graph unweighted) + @Test + public void setEdgeWeight() { - assertEquals( - WeightedGraph.DEFAULT_EDGE_WEIGHT, - unweighted.getEdgeWeight( - unweighted.getEdge(v1, v2))); + try { + this.unweightedGraph.setEdgeWeight(e23, 81); + } catch (UnsupportedOperationException e) { + assertThat(e.getMessage(), is("Edge weight is not supported")); + } + } - Graphs.addEdge(weighted, v2, v3, 5.0); - assertEquals( - WeightedGraph.DEFAULT_EDGE_WEIGHT, - unweighted.getEdgeWeight( - unweighted.getEdge(v2, v3))); + @Test + public void getType() + { + assertFalse(this.unweightedGraph.getType().isWeighted()); + } - unweighted.addEdge(v3, v1); - assertEquals( - WeightedGraph.DEFAULT_EDGE_WEIGHT, - unweighted.getEdgeWeight( - unweighted.getEdge(v3, v1))); - assertEquals( - WeightedGraph.DEFAULT_EDGE_WEIGHT, - weighted.getEdgeWeight( - weighted.getEdge(v3, v1))); + @Test + public void failOnCreationOfUnweightedGraph() + { + try { + new AsUnweightedGraph<>(null); + fail("Expected an NullPointerException to be thrown"); + } catch (NullPointerException e) { + assertNotNull(e); + } } } - -// End AsUnweightedGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/AsWeightedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/AsWeightedGraphTest.java deleted file mode 100644 index 09c992b49ad..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/AsWeightedGraphTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * AsWeightedGraphTest.java - * -------------------------- - * (C) Copyright 2007, by Lucas J. Scharenbroich and Contributors. - * - * Original Author: Lucas J. Scharenbroich - * Contributor(s): John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 22-Sep-2007 : Initial revision (JVS); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A unit test for the AsWeightedGraph view. - * - * @author Lucas J. Scharenbroich - */ -public class AsWeightedGraphTest - extends EnhancedTestCase -{ - //~ Instance fields -------------------------------------------------------- - - public SimpleWeightedGraph weightedGraph; - public SimpleGraph unweightedGraph; - - //~ Methods ---------------------------------------------------------------- - - public void setUp() - { - weightedGraph = - new SimpleWeightedGraph( - DefaultWeightedEdge.class); - unweightedGraph = - new SimpleGraph(DefaultEdge.class); - - // Create a very simple graph - weightedGraph.addVertex("v1"); - weightedGraph.addVertex("v2"); - weightedGraph.addVertex("v3"); - - unweightedGraph.addVertex("v1"); - unweightedGraph.addVertex("v2"); - unweightedGraph.addVertex("v3"); - - weightedGraph.setEdgeWeight(weightedGraph.addEdge("v1", "v2"), 1.); - weightedGraph.setEdgeWeight(weightedGraph.addEdge("v2", "v3"), 2.); - weightedGraph.setEdgeWeight(weightedGraph.addEdge("v3", "v1"), 3.); - - unweightedGraph.addEdge("v1", "v2"); - unweightedGraph.addEdge("v2", "v3"); - unweightedGraph.addEdge("v3", "v1"); - } - - public void tearDown() - { - } - - public void test1() - { - Map weightMap1 = - new HashMap(); - Map weightMap2 = - new HashMap(); - - DefaultEdge e1 = unweightedGraph.getEdge("v1", "v2"); - DefaultEdge e2 = unweightedGraph.getEdge("v2", "v3"); - DefaultEdge e3 = unweightedGraph.getEdge("v3", "v1"); - - DefaultWeightedEdge e4 = weightedGraph.getEdge("v1", "v2"); - DefaultWeightedEdge e5 = weightedGraph.getEdge("v2", "v3"); - DefaultWeightedEdge e6 = weightedGraph.getEdge("v3", "v1"); - - weightMap1.put(e1, 9.0); - - weightMap2.put(e4, 9.0); - weightMap2.put(e6, 8.0); - - assertEquals( - unweightedGraph.getEdgeWeight(e1), - WeightedGraph.DEFAULT_EDGE_WEIGHT); - - WeightedGraph g1 = - new AsWeightedGraph( - unweightedGraph, - weightMap1); - WeightedGraph g2 = - new AsWeightedGraph( - weightedGraph, - weightMap2); - - assertEquals(g1.getEdgeWeight(e1), 9.0); - assertEquals(g1.getEdgeWeight(e2), WeightedGraph.DEFAULT_EDGE_WEIGHT); - assertEquals(g1.getEdgeWeight(e3), WeightedGraph.DEFAULT_EDGE_WEIGHT); - - assertEquals(g2.getEdgeWeight(e4), 9.0); - assertEquals(g2.getEdgeWeight(e5), 2.0); - assertEquals(g2.getEdgeWeight(e6), 8.0); - - g1.setEdgeWeight(e2, 5.0); - g2.setEdgeWeight(e5, 5.0); - - assertEquals(g1.getEdgeWeight(e2), 5.0); - assertEquals( - unweightedGraph.getEdgeWeight(e2), - WeightedGraph.DEFAULT_EDGE_WEIGHT); - - assertEquals(g2.getEdgeWeight(e5), 5.0); - assertEquals(weightedGraph.getEdgeWeight(e5), 5.0); - } -} - -// End AsWeightedGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/CloneTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/CloneTest.java index 7bae3e8f633..8ec8004f483 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/CloneTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/CloneTest.java @@ -1,76 +1,41 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* -------------- - * CloneTest.java - * -------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 06-Oct-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; /** * A unit test for a cloning bug, adapted from a forum entry from Linda Buisman. * * @author John V. Sichi - * @since Oct 6, 2003 */ public class CloneTest - extends EnhancedTestCase { - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public CloneTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- - /** * Test graph cloning. */ @SuppressWarnings("unchecked") + @Test public void testCloneSpecificsBug() { - SimpleGraph g1 = - new SimpleGraph(DefaultEdge.class); + SimpleGraph g1 = new SimpleGraph<>(DefaultEdge.class); String one = "1"; String two = "2"; String three = "3"; @@ -80,10 +45,11 @@ public void testCloneSpecificsBug() g1.addEdge(one, two); g1.addEdge(two, three); - SimpleGraph g2 = - (SimpleGraph) g1.clone(); // Type-safty - // warning OK with - // clone + 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))); @@ -92,32 +58,29 @@ public void testCloneSpecificsBug() } /** - * Tests usage of {@link ParanoidGraph} for detecting broken vertex - * implementations. + * 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); + SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); + ParanoidGraph pg = new ParanoidGraph<>(g); pg.addVertex(v1); pg.addVertex(v2); try { pg.addVertex(v3); - // should not get here - assertFalse(); + fail(); // should not get here } catch (IllegalArgumentException ex) { // expected, swallow } } - //~ Inner Classes ---------------------------------------------------------- + // ~ Inner Classes ---------------------------------------------------------- private class BrokenVertex { @@ -128,14 +91,16 @@ private class BrokenVertex this.x = x; } + @Override public boolean equals(Object other) { - if (!(other instanceof BrokenVertex)) { - return false; - } - return x == ((BrokenVertex) other).x; + return other instanceof BrokenVertex && x == ((BrokenVertex) other).x; + } + + @Override + public int hashCode() + { + return super.hashCode(); } } } - -// End CloneTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/DefaultDirectedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/DefaultDirectedGraphTest.java deleted file mode 100644 index 76194c51eb1..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/DefaultDirectedGraphTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------------- - * DefaultDirectedGraphTest.java - * ----------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 09-Aug-2003 : Initial revision (BN); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A unit test for directed multigraph. - * - * @author Barak Naveh - * @since Aug 9, 2003 - */ -public class DefaultDirectedGraphTest - extends EnhancedTestCase -{ - //~ Instance fields -------------------------------------------------------- - - private String v1 = "v1"; - private String v2 = "v2"; - private String v3 = "v3"; - - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testEdgeSetFactory() - { - DirectedMultigraph g = - new DirectedMultigraph( - DefaultEdge.class); - g.setEdgeSetFactory(new LinkedHashSetFactory()); - initMultiTriangleWithMultiLoop(g); - } - - /** - * . - */ - public void testEdgeOrderDeterminism() - { - DirectedGraph 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()); - } - - /** - * . - */ - public void testEdgesOf() - { - DirectedGraph g = - createMultiTriangleWithMultiLoop(); - - assertEquals(3, g.edgesOf(v1).size()); - assertEquals(2, g.edgesOf(v2).size()); - } - - /** - * . - */ - public void testGetAllEdges() - { - DirectedGraph g = - createMultiTriangleWithMultiLoop(); - - Set loops = g.getAllEdges(v1, v1); - assertEquals(1, loops.size()); - } - - /** - * . - */ - public void testInDegreeOf() - { - DirectedGraph g = - createMultiTriangleWithMultiLoop(); - - assertEquals(2, g.inDegreeOf(v1)); - assertEquals(1, g.inDegreeOf(v2)); - } - - /** - * . - */ - public void testOutDegreeOf() - { - DirectedGraph g = - createMultiTriangleWithMultiLoop(); - - assertEquals(2, g.outDegreeOf(v1)); - assertEquals(1, g.outDegreeOf(v2)); - } - - /** - * . - */ - public void testVertexOrderDeterminism() - { - DirectedGraph g = - createMultiTriangleWithMultiLoop(); - Iterator iter = g.vertexSet().iterator(); - assertEquals(v1, iter.next()); - assertEquals(v2, iter.next()); - assertEquals(v3, iter.next()); - } - - private DirectedGraph - createMultiTriangleWithMultiLoop() - { - DirectedGraph g = - new DirectedMultigraph( - DefaultEdge.class); - initMultiTriangleWithMultiLoop(g); - - return g; - } - - private void initMultiTriangleWithMultiLoop( - DirectedGraph g) - { - g.addVertex(v1); - g.addVertex(v2); - g.addVertex(v3); - - g.addEdge(v1, v1); - g.addEdge(v1, v2); - g.addEdge(v2, v3); - g.addEdge(v3, v1); - } - - //~ Inner Classes ---------------------------------------------------------- - - private static class LinkedHashSetFactory - implements EdgeSetFactory - { - /** - * . - * - * @param vertex - * - * @return an empty list. - */ - public Set createEdgeSet(V vertex) - { - return new LinkedHashSet(); - } - } -} - -// End DefaultDirectedGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedAcyclicGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedAcyclicGraphTest.java new file mode 100644 index 00000000000..26c403091ef --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedAcyclicGraphTest.java @@ -0,0 +1,781 @@ +/* + * (C) Copyright 2008-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 (GraphCycleProhibitedException 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); + } + + } + + @Test + public void testFastLookupGraphSpecificsStrategyAndArrayUnenforcedSet() + { + GraphSpecificsStrategy graphSpecificsStrategy = + new FastLookupGraphSpecificsStrategy<>(); + DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( + SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false, + false, graphSpecificsStrategy); + String a = "A"; + String b = "B"; + + graph.addVertex(a); + graph.addVertex(b); + + graph.addEdge(a, b); + + Iterator it = graph.iterator(); + assertEquals(a, it.next()); + assertEquals(b, it.next()); + } + + @Test + public void testFastLookupGraphSpecificsStrategyAndHashSet() + { + GraphSpecificsStrategy graphSpecificsStrategy = + new FastLookupGraphSpecificsStrategy<>() + { + @Override + public EdgeSetFactory getEdgeSetFactory() + { + return vertex -> new HashSet<>(); + } + }; + DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( + SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false, + false, graphSpecificsStrategy); + String a = "A"; + String b = "B"; + + graph.addVertex(a); + graph.addVertex(b); + + graph.addEdge(a, b); + + Iterator it = graph.iterator(); + assertEquals(a, it.next()); + assertEquals(b, it.next()); + } + + // ~ 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 + } + } + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedGraphTest.java new file mode 100644 index 00000000000..ca5e90bbf88 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedGraphTest.java @@ -0,0 +1,190 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/EqualsAndHashCodeTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/EqualsAndHashCodeTest.java new file mode 100644 index 00000000000..cc861ef63d1 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/EqualsAndHashCodeTest.java @@ -0,0 +1,374 @@ +/* + * (C) Copyright 2012-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/GenericGraphsTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/GenericGraphsTest.java index 3571ebda8fe..cac299bca52 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/GenericGraphsTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/GenericGraphsTest.java @@ -1,46 +1,27 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------------------- - * GenericGraphsTest.java - * -------------------------- - * (C) Copyright 2006-2008, by HartmutBenz and Contributors. +/* + * (C) Copyright 2006-2023, by HartmutBenz and Contributors. * - * Original Author: Hartmut Benz - * Contributor(s): John V. Sichi + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * ??-???-2006 : Initial revision (HB); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * A unit test for graph generic vertex/edge parameters. @@ -48,29 +29,14 @@ * @author Hartmut Benz */ public class GenericGraphsTest - extends EnhancedTestCase { - //~ Instance fields -------------------------------------------------------- + // ~ Instance fields -------------------------------------------------------- Graph objectGraph; Graph fooFooGraph; Graph barBarGraph; - Graph fooBarGraph; - - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public GenericGraphsTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- - - // ~ Methods --------------------------------------------------------------- + @Test public void testLegalInsertStringGraph() { String v1 = "Vertex1"; @@ -80,6 +46,7 @@ public void testLegalInsertStringGraph() objectGraph.addEdge(v1, v2); } + @Test public void testLegalInsertFooGraph() { FooVertex v1 = new FooVertex(); @@ -98,6 +65,7 @@ public void testLegalInsertFooGraph() fooFooGraph.addEdge(vb1, vb2, new BarEdge()); } + @Test public void testLegalInsertBarGraph() { BarVertex v1 = new BarVertex(); @@ -107,6 +75,7 @@ public void testLegalInsertBarGraph() barBarGraph.addEdge(v1, v2); } + @Test public void testLegalInsertFooBarGraph() { FooVertex v1 = new FooVertex(); @@ -122,10 +91,10 @@ public void testLegalInsertFooBarGraph() fooFooGraph.addEdge(v1, vb2); } + @Test public void testAlissaHacker() { - DirectedGraph g = - new DefaultDirectedGraph(CustomEdge.class); + Graph g = new DefaultDirectedGraph<>(CustomEdge.class); g.addVertex("a"); g.addVertex("b"); g.addEdge("a", "b"); @@ -134,6 +103,7 @@ public void testAlissaHacker() assertEquals("Alissa P. Hacker approves the edge from a to b", s); } + @Test public void testEqualButNotSameVertex() { EquivVertex v1 = new EquivVertex(); @@ -149,36 +119,37 @@ public void testEqualButNotSameVertex() /** * . */ - protected void setUp() + @BeforeEach + public void setUp() { - objectGraph = - new DefaultDirectedGraph( - DefaultEdge.class); - fooFooGraph = new SimpleGraph(FooEdge.class); - barBarGraph = new SimpleGraph(BarEdge.class); + objectGraph = new DefaultDirectedGraph<>(DefaultEdge.class); + fooFooGraph = new SimpleGraph<>(FooEdge.class); + barBarGraph = new SimpleGraph<>(BarEdge.class); } - //~ Inner Classes ---------------------------------------------------------- + // ~ 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(); + 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; @@ -187,19 +158,15 @@ public int hashCode() public static class EquivGraph extends AbstractBaseGraph - implements UndirectedGraph { - /** - */ private static final long serialVersionUID = 8647217182401022498L; public EquivGraph() { super( - new ClassBasedEdgeFactory( - DefaultEdge.class), - true, - true); + SupplierUtil.createSupplier(EquivVertex.class), + SupplierUtil.createSupplier(DefaultEdge.class), + DefaultGraphType.directedPseudograph().asUnweighted()); } } @@ -223,6 +190,11 @@ public FooVertex(String s) { str = s; } + + public String toString() + { + return str; + } } public static class BarEdge @@ -239,11 +211,5 @@ public BarVertex() super("empty bar"); } - public BarVertex(String s) - { - super(s); - } } } - -// End GenericGraphsTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/GraphWalkTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/GraphWalkTest.java new file mode 100644 index 00000000000..a63538c9946 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/GraphWalkTest.java @@ -0,0 +1,336 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author Joris Kinable + */ +public class GraphWalkTest +{ + + @Test + public void testInvalidPath1() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testInvalidPath2() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testInvalidPath3() + { + assertThrows(InvalidGraphWalkException.class, () -> { + 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 + public void testInvalidPath4() + { + assertThrows(InvalidGraphWalkException.class, () -> { + 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 + public void testInvalidPath5() + { + assertThrows(InvalidGraphWalkException.class, () -> { + 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) { + assertEquals(0, path.getLength()); + assertEquals(Collections.emptyList(), path.getVertexList()); + assertEquals(Collections.emptyList(), path.getEdgeList()); + assertTrue(path.isEmpty()); + 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); + assertEquals(0, p1.getStartVertex()); + assertEquals(4, p1.getEndVertex()); + assertEquals(vertexList, p1.getVertexList()); + assertEquals(edgeList.size(), p1.getLength()); + assertEquals(10.0, p1.getWeight(), 0.0000000001); + + GraphPath p2 = new GraphWalk<>(completeGraph, vertexList, 10); + assertEquals(0, p2.getStartVertex()); + assertEquals(4, p2.getEndVertex()); + assertEquals(edgeList, p2.getEdgeList()); + assertEquals(edgeList.size(), p2.getLength()); + 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); + assertEquals(revPath, rev1); + assertEquals(revPath, rev2); + + rev1 = gw1.reverse(); + assertEquals(9.0, gw1.getWeight(), 0.0000000001); + rev2 = gw2.reverse(); + assertEquals(9.0, gw2.getWeight(), 0.0000000001); + } + + @Test + public void testReverseInvalidPathDirected() + { + assertThrows(InvalidGraphWalkException.class, () -> { + 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); + + assertEquals(revPath, rev1); + assertEquals(15, rev1.getWeight(), 0.00000001); + + GraphWalk rev2 = gw1.reverse(); + assertEquals(15, rev2.getWeight(), 0.00000001); + } + + /** + * Cannot extend empty path + */ + @Test + public void testIllegalConcatPath1() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testIllegalConcatPath2() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + assertEquals(expected, gw3); + 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. + 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); + assertNotEquals(gw1, gw2); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/IncomingOutgoingEdgesTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/IncomingOutgoingEdgesTest.java new file mode 100644 index 00000000000..e3cc890b9b9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/IncomingOutgoingEdgesTest.java @@ -0,0 +1,295 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/ListenableGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/ListenableGraphTest.java index fd586a3d37f..b00e26db1e9 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/ListenableGraphTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/ListenableGraphTest.java @@ -1,90 +1,57 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------ - * ListenableGraphTest.java - * ------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 03-Aug-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 junit.framework.*; - import org.jgrapht.*; import org.jgrapht.event.*; +import org.jgrapht.graph.builder.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit test for {@link ListenableGraph} class. * * @author Barak Naveh - * @since Aug 3, 2003 */ public class ListenableGraphTest - extends TestCase { - //~ Instance fields -------------------------------------------------------- + // ~ Instance fields -------------------------------------------------------- - DefaultEdge lastAddedEdge; - DefaultEdge lastRemovedEdge; + Object lastAddedEdge; + Object lastRemovedEdge; Object lastAddedVertex; Object lastRemovedVertex; - - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public ListenableGraphTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- + Double lastWeightUpdate; /** * Tests GraphListener listener. */ + @Test public void testGraphListener() { init(); ListenableGraph g = - new ListenableUndirectedGraph( - DefaultEdge.class); - GraphListener listener = new MyGraphListner(); + new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); + GraphListener listener = new MyGraphListener<>(); g.addGraphListener(listener); String v1 = "v1"; @@ -137,14 +104,14 @@ public void testGraphListener() /** * Tests VertexSetListener listener. */ + @Test public void testVertexSetListener() { init(); ListenableGraph g = - new ListenableUndirectedGraph( - DefaultEdge.class); - VertexSetListener listener = new MyGraphListner(); + new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); + VertexSetListener listener = new MyGraphListener<>(); g.addVertexSetListener(listener); String v1 = "v1"; @@ -194,57 +161,179 @@ public void testVertexSetListener() assertEquals(null, lastRemovedVertex); } - private void init() + /** + * 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 ---------------------------------------------------------- + // ~ Inner Classes ---------------------------------------------------------- /** * A listener on the tested graph. * * @author Barak Naveh - * @since Aug 3, 2003 */ - private class MyGraphListner - implements GraphListener + private class MyGraphListener + implements GraphListener { - /** - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public void edgeAdded(GraphEdgeChangeEvent e) + @Override + public void edgeAdded(GraphEdgeChangeEvent e) { lastAddedEdge = e.getEdge(); } - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) + @Override + public void edgeRemoved(GraphEdgeChangeEvent e) { lastRemovedEdge = e.getEdge(); } - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ + @Override public void vertexAdded(GraphVertexChangeEvent e) { lastAddedVertex = e.getVertex(); } - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ + @Override public void vertexRemoved(GraphVertexChangeEvent e) { lastRemovedVertex = e.getVertex(); } + + @Override + public void edgeWeightUpdated(GraphEdgeChangeEvent e) + { + lastWeightUpdate = e.getEdgeWeight(); + } } -} -// End ListenableGraphTest.java + /** + * 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; + } + + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/MaskEdgeSetTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskEdgeSetTest.java new file mode 100644 index 00000000000..7234df6acb8 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskEdgeSetTest.java @@ -0,0 +1,105 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeEach + 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 + @SuppressWarnings("unlikely-arg-type") + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/MaskSubgraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskSubgraphTest.java new file mode 100644 index 00000000000..72f0292d60e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskSubgraphTest.java @@ -0,0 +1,145 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/MaskVertexSetTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskVertexSetTest.java new file mode 100644 index 00000000000..b24e26fcf51 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/MaskVertexSetTest.java @@ -0,0 +1,95 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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; + + @BeforeEach + 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 + @SuppressWarnings("unlikely-arg-type") + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTest.java index b9aed9e488f..520e4ac5c77 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTest.java @@ -1,120 +1,605 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* -------------- - * SerializationTest.java - * -------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 06-Oct-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 org.jgrapht.*; +import org.junit.jupiter.api.*; +import java.util.*; +import java.util.stream.*; + +import static org.jgrapht.graph.SerializationTestUtils.serializeAndDeserialize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.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 - extends EnhancedTestCase { - //~ Instance fields -------------------------------------------------------- + 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), 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) + assertInstanceOf(DefaultWeightedEdge.class, e); + 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); - private String v1 = "v1"; - private String v2 = "v2"; - private String v3 = "v3"; + verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); - //~ Constructors ----------------------------------------------------------- + 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); + } /** - * @see junit.framework.TestCase#TestCase(java.lang.String) + * Tests serialization of {@link DefaultUndirectedWeightedGraph} + *

    + * undirected self-loops no multiple edges weighted */ - public SerializationTest(String name) + @Test + public void testDefaultUndirectedWeightedGraph() + throws Exception { - super(name); + 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); } - //~ Methods ---------------------------------------------------------------- + /** + * 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 DirectedMultigraph. + * Tests serialization of {@link DirectedMultigraph} + *

    + * directed no-self loops multiple edges unweighted */ - @SuppressWarnings("unchecked") + @Test public void testDirectedMultigraph() throws Exception { - DirectedMultigraph graph = - new DirectedMultigraph( - DefaultEdge.class); - graph.addVertex(v1); - graph.addVertex(v2); - graph.addVertex(v3); - graph.addEdge(v1, v2); - graph.addEdge(v2, v3); - graph.addEdge(v2, v3); + 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); - graph = - (DirectedMultigraph) serializeAndDeserialize( - graph); - assertTrue(graph.containsVertex(v1)); - assertTrue(graph.containsVertex(v2)); - assertTrue(graph.containsVertex(v3)); - assertTrue(graph.containsEdge(v1, v2)); - assertTrue(graph.containsEdge(v2, v3)); - assertEquals(1, graph.edgesOf(v1).size()); - assertEquals(3, graph.edgesOf(v2).size()); - assertEquals(2, graph.edgesOf(v3).size()); + verifyBasic(graph1, graph2, Arrays.asList(1, 3, 2)); } - private Object serializeAndDeserialize(Object obj) + /** + * Tests serialization of {@link DirectedPseudograph} + *

    + * directed self-loops multiple-edges unweighted + */ + @Test + public void testDirectedPseudograph() throws Exception { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bout); + DirectedPseudograph graph1 = + new DirectedPseudograph<>(DefaultEdge.class); + Graphs.addAllVertices(graph1, VERTEX_LIST); - out.writeObject(obj); - out.flush(); + graph1.addEdge(V1, V2); + graph1.addEdge(V1, V2); // multi-edge - ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bin); + graph1.addEdge(V2, V3); + graph1.addEdge(V1, V1); // self-loop + graph1.addEdge(V1, V3); - obj = in.readObject(); - return obj; + DirectedPseudograph graph2 = serializeAndDeserialize(graph1); + + verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); } -} -// End SerializationTest.java + /** + * 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)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTestUtils.java b/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTestUtils.java new file mode 100644 index 00000000000..1576f436589 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTestUtils.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2003-2023, 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleDirectedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleDirectedGraphTest.java index d236d5d193d..1a9a7d4fa9c 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleDirectedGraphTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleDirectedGraphTest.java @@ -1,146 +1,107 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ---------------------------- - * SimpleDirectedGraphTest.java - * ---------------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 25-Jul-2003 : Initial revision (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; +import org.jgrapht.util.*; +import org.junit.jupiter.api.*; + +import java.util.*; +import java.util.function.*; +import static org.junit.jupiter.api.Assertions.*; /** * A unit test for simple directed graph. * * @author Barak Naveh - * @since Jul 25, 2003 */ public class SimpleDirectedGraphTest - extends EnhancedTestCase { - //~ Instance fields -------------------------------------------------------- + // ~ Instance fields -------------------------------------------------------- - DirectedGraph gEmpty; - private DirectedGraph g1; - private DirectedGraph g2; - private DirectedGraph g3; - private DirectedGraph g4; + Graph gEmpty; + private Graph g1; + private Graph g2; + private Graph g3; + private Graph g4; private DefaultEdge eLoop; - private EdgeFactory eFactory; - private String v1 = "v1"; - private String v2 = "v2"; - private String v3 = "v3"; - private String v4 = "v4"; - - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public SimpleDirectedGraphTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- + 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() { - init(); + // loops not allowed + assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v1, v1, eLoop)); - try { - g1.addEdge(v1, v1, eLoop); // loops not allowed - assertFalse(); - } catch (IllegalArgumentException e) { - assertTrue(); - } + assertThrows(NullPointerException.class, () -> g3.addEdge(v1, v1, null)); - try { - g3.addEdge(v1, v1, null); - assertFalse(); // NPE - } catch (NullPointerException e) { - assertTrue(); - } + DefaultEdge e = eSupplier.get(); - DefaultEdge e = eFactory.createEdge(v2, v1); + // no such vertex in graph + assertThrows(IllegalArgumentException.class, () -> g1.addEdge("ya", "ya", e)); - try { - g1.addEdge("ya", "ya", e); // no such vertex in graph - assertFalse(); - } catch (IllegalArgumentException ile) { - assertTrue(); - } + // supplied edge already in another graph with differing touching vertices + assertThrows(IntrusiveEdgeException.class, () -> g4.addEdge(v1, v3, e12_1)); - assertEquals(false, g2.addEdge(v2, v1, e)); - assertEquals(false, g3.addEdge(v2, v1, e)); - assertEquals(true, g4.addEdge(v2, v1, e)); + 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() { - init(); + // loops not allowed + assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v1, v1)); - try { - g1.addEdge(v1, v1); // loops not allowed - assertFalse(); - } catch (IllegalArgumentException e) { - assertTrue(); - } + assertThrows(NullPointerException.class, () -> g3.addEdge(null, null)); - try { - g3.addEdge(null, null); - assertFalse(); // NPE - } catch (NullPointerException e) { - assertTrue(); - } + // no such vertex in graph + assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v2, v1)); - try { - g1.addEdge(v2, v1); // no such vertex in graph - assertFalse(); - } catch (IllegalArgumentException ile) { - assertTrue(); - } + // 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)); @@ -150,10 +111,9 @@ public void testAddEdgeObjectObject() /** * . */ + @Test public void testAddVertex() { - init(); - assertEquals(1, g1.vertexSet().size()); assertEquals(2, g2.vertexSet().size()); assertEquals(3, g3.vertexSet().size()); @@ -167,20 +127,31 @@ public void testAddVertex() /** * Class to test for boolean containsEdge(Edge) */ + @Test public void testContainsEdgeEdge() { - init(); - - // TODO Implement containsEdge(). + 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() { - init(); - assertFalse(g1.containsEdge(v1, v2)); assertFalse(g1.containsEdge(v1, v1)); @@ -205,30 +176,60 @@ public void testContainsEdgeObjectObject() /** * . */ + @Test public void testContainsVertex() { - init(); - - // TODO Implement containsVertex(). + 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() { - init(); - - // TODO Implement edgeSet(). + 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() { - init(); - assertEquals(g4.edgesOf(v1).size(), 2); assertEquals(g3.edgesOf(v1).size(), 4); @@ -246,34 +247,70 @@ public void testEdgesOf() /** * . */ + @Test public void testGetAllEdges() { - init(); // TODO Implement getAllEdges(). + 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() { - init(); // TODO Implement getEdge(). + 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)); } /** * . */ - public void testGetEdgeFactory() + @Test + public void testGetEdgeSupplier() { - init(); // TODO Implement getEdgeFactory(). + assertNotNull(g1.getEdgeSupplier()); + Supplier es = g1.getEdgeSupplier(); + DefaultEdge e = es.get(); + assertNotNull(e); + assertNull(g1.getEdgeSource(e)); + assertNull(g1.getEdgeTarget(e)); } /** * . */ - public void testInDegreeOf() + @Test + public void testGetVertexSupplier() { - init(); + 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)); @@ -290,26 +327,23 @@ public void testInDegreeOf() try { g3.inDegreeOf(new String()); - assertFalse(); + fail("Should not get here."); } catch (IllegalArgumentException e) { - assertTrue(); } try { g3.inDegreeOf(null); - assertFalse(); + fail("Should not get here."); } catch (NullPointerException e) { - assertTrue(); } } /** * . */ + @Test public void testIncomingOutgoingEdgesOf() { - init(); - Set e1to2 = g2.outgoingEdgesOf(v1); Set e2from1 = g2.incomingEdgesOf(v2); assertEquals(e1to2, e2from1); @@ -318,26 +352,59 @@ public void testIncomingOutgoingEdgesOf() /** * . */ + @Test public void testOutDegreeOf() { - init(); // TODO Implement outDegreeOf(). + 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() { - init(); // TODO Implement outgoingEdgesOf(). + 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() { - init(); - assertEquals(g4.edgeSet().size(), 4); g4.removeEdge(v1, v2); assertEquals(g4.edgeSet().size(), 3); @@ -349,17 +416,45 @@ public void testRemoveEdgeEdge() /** * Class to test for Edge removeEdge(Object, Object) */ + @Test public void testRemoveEdgeObjectObject() { - init(); // TODO Implement removeEdge(). + 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() { - init(); assertEquals(4, g4.vertexSet().size()); assertTrue(g4.removeVertex(v1)); assertEquals(3, g4.vertexSet().size()); @@ -378,19 +473,33 @@ public void testRemoveVertex() /** * . */ + @Test public void testVertexSet() { - init(); // TODO Implement vertexSet(). + 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() { - init(); - - DirectedGraph g = - new SimpleDirectedGraph(DefaultEdge.class); - DirectedGraph r = - new EdgeReversedGraph(g); + Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); + Graph r = new EdgeReversedGraph<>(g); g.addVertex(v1); g.addVertex(v2); @@ -416,9 +525,7 @@ public void testReversedView() } private void verifyReversal( - DirectedGraph g, - DirectedGraph r, - DefaultEdge e) + Graph g, Graph r, DefaultEdge e) { assertTrue(r.containsVertex(v1)); assertTrue(r.containsVertex(v2)); @@ -454,49 +561,52 @@ private void verifyReversal( assertEquals("([v1, v2], [(v2,v1)])", r.toString()); } - private void init() + @BeforeEach + public void setUp() { - gEmpty = - new SimpleDirectedGraph( - DefaultEdge.class); - g1 = new SimpleDirectedGraph( - DefaultEdge.class); - g2 = new SimpleDirectedGraph( - DefaultEdge.class); - g3 = new SimpleDirectedGraph( - DefaultEdge.class); - g4 = new SimpleDirectedGraph( - DefaultEdge.class); - - eFactory = g1.getEdgeFactory(); - eLoop = eFactory.createEdge(v1, v1); + 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); - g2.addEdge(v1, v2); - g2.addEdge(v2, v1); + e12_1 = g2.addEdge(v1, v2); + e21_1 = g2.addEdge(v2, v1); g3.addVertex(v1); g3.addVertex(v2); g3.addVertex(v3); - g3.addEdge(v1, v2); - g3.addEdge(v2, v1); - g3.addEdge(v2, v3); - g3.addEdge(v3, v2); - g3.addEdge(v3, v1); - g3.addEdge(v1, 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); - g4.addEdge(v1, v2); - g4.addEdge(v2, v3); - g4.addEdge(v3, v4); - g4.addEdge(v4, v1); + e12_3 = g4.addEdge(v1, v2); + e23_2 = g4.addEdge(v2, v3); + e34_1 = g4.addEdge(v3, v4); + e41_1 = g4.addEdge(v4, v1); } } - -// End SimpleDirectedGraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleIdentityDirectedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleIdentityDirectedGraphTest.java new file mode 100644 index 00000000000..b1604900165 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleIdentityDirectedGraphTest.java @@ -0,0 +1,682 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + fail("Should not get here."); + } catch (IllegalArgumentException e) { + } + + try { + g3.addEdge(v1, v1, null); + 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 + 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 + fail("Should not get here."); + } catch (IllegalArgumentException e) { + } + + try { + g3.addEdge(null, null); + fail("Should not get here."); + } catch (NullPointerException e) { + } + + try { + g1.addEdge(v2, v1); // no such vertex in graph + 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<>("")); + fail("Should not get here."); + } catch (IllegalArgumentException e) { + } + + try { + g3.inDegreeOf(null); + 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)); + } + + @BeforeEach + 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"); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/SubgraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/SubgraphTest.java deleted file mode 100644 index 1bd956c252a..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/graph/SubgraphTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------ - * SubgraphTest.java - * ------------------------ - * (C) Copyright 2003-2008, by Michael Behrisch and Contributors. - * - * Original Author: Michael Behrisch - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 21-Sep-2004 : Initial revision (MB); - * - */ -package org.jgrapht.graph; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; - - -/** - * Unit test for {@link Subgraph} class. - * - * @author Michael Behrisch - * @since Sep 21, 2004 - */ -public class SubgraphTest - extends TestCase -{ - //~ Instance fields -------------------------------------------------------- - - private String v1 = "v1"; - private String v2 = "v2"; - private String v3 = "v3"; - private String v4 = "v4"; - - //~ Constructors ----------------------------------------------------------- - - /** - * @see junit.framework.TestCase#TestCase(java.lang.String) - */ - public SubgraphTest(String name) - { - super(name); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * . - */ - public void testInducedSubgraphListener() - { - UndirectedGraph g = init(true); - UndirectedSubgraph sub = - new UndirectedSubgraph(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. - */ - public void testSubgraph() - { - UndirectedGraph g = init(false); - UndirectedSubgraph sub = - new UndirectedSubgraph(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 UndirectedSubgraph(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 UndirectedSubgraph( - g, - vset, - new HashSet(g.getAllEdges(v1, v2))); - assertEquals(vset, sub.vertexSet()); - assertEquals(1, sub.edgeSet().size()); - } - - /** - * . - */ - public void testSubgraphListener() - { - UndirectedGraph g = init(true); - UndirectedSubgraph sub = - new UndirectedSubgraph(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 UndirectedGraph init(boolean listenable) - { - UndirectedGraph g; - - if (listenable) { - g = new ListenableUndirectedGraph( - 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; - } - - public void testInducedSubgraphUnderlyingEdgeAddition() - { - ListenableGraph baseGraph = - new ListenableUndirectedGraph( - DefaultEdge.class); - - baseGraph.addVertex(v1); - baseGraph.addVertex(v2); - - Set initialVertexes = new LinkedHashSet(); - initialVertexes.add(v1); - Subgraph> subgraph = - new Subgraph>( - baseGraph, - initialVertexes, - null); - baseGraph.addEdge(v1, v2); - - assertFalse(subgraph.containsEdge(v1, v2)); - } -} - -// End SubgraphTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/TestEdge.java b/jgrapht-core/src/test/java/org/jgrapht/graph/TestEdge.java new file mode 100644 index 00000000000..96efdf8791a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/TestEdge.java @@ -0,0 +1,58 @@ +/* + * (C) Copyright 2003-2023, 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); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/UnweightedGraphAsWeightedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/UnweightedGraphAsWeightedGraphTest.java new file mode 100644 index 00000000000..2526abc6a2e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/UnweightedGraphAsWeightedGraphTest.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.jgrapht.Graph.DEFAULT_EDGE_WEIGHT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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}. + */ + @BeforeEach + 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() + { + assertThrows(NullPointerException.class, () -> this.weightedGraph.getEdgeWeight(null)); + } + + @Test + public void testGetType() + { + assertTrue(this.weightedGraph.getType().isWeighted()); + } + + @Test + public void createAsWeightedGraphWithWeightPropagationOnAnUnweightedGraph() + { + assertThrows(IllegalArgumentException.class, () -> new AsWeightedGraph<>( + new DefaultUndirectedGraph<>(String.class), new HashMap<>(), true)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphAsWeightedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphAsWeightedGraphTest.java new file mode 100644 index 00000000000..6e2eae68831 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphAsWeightedGraphTest.java @@ -0,0 +1,171 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +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); + assertThrows(NullPointerException.class, () -> this.weightedGraph.getEdgeWeight(null)); + } + + @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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphTest.java new file mode 100644 index 00000000000..cf4ab24efde --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphTest.java @@ -0,0 +1,101 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testInvalidEdgeOnWeightedGraph() + { + assertThrows(IllegalArgumentException.class, () -> { + WeightedPseudograph g = + new WeightedPseudograph(String.class); + g.getEdgeWeight("1-2"); + }); + } + + @Test + public void testInvalidEdgeOnWeightedGraphSet() + { + assertThrows(IllegalArgumentException.class, () -> { + 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(1, g.getEdgeSource(e)); + assertEquals(2, g.getEdgeTarget(e)); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphBuilderTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphBuilderTest.java new file mode 100644 index 00000000000..7d7e22ca107 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphBuilderTest.java @@ -0,0 +1,149 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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))); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphTypeBuilderTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphTypeBuilderTest.java new file mode 100644 index 00000000000..9388e5f5274 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphTypeBuilderTest.java @@ -0,0 +1,104 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/graph/concurrent/AsSynchronizedGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/graph/concurrent/AsSynchronizedGraphTest.java new file mode 100644 index 00000000000..d9c8973da63 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/graph/concurrent/AsSynchronizedGraphTest.java @@ -0,0 +1,497 @@ +/* + * (C) Copyright 2008-2023, 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.graph.*; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.*; + +import java.util.*; +import java.util.concurrent.Semaphore; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class AsSynchronizedGraph. + * + * @author CHEN Kui + */ +@Execution(ExecutionMode.SAME_THREAD) +public class AsSynchronizedGraphTest +{ + private List vertices; + private List edges; + private AsSynchronizedGraph g; + private List> ordersList; + + @BeforeEach + public void setup() { + vertices = Collections.synchronizedList(new ArrayList<>()); + edges = Collections.synchronizedList(new ArrayList<>()); + ordersList = Collections.synchronizedList(new ArrayList<>()); + } + + @Test + public void testAddVertex() + { + g = new AsSynchronizedGraph.Builder() + .build(new SimpleGraph<>(DefaultEdge.class)); + 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)); + } + final Semaphore semaphore = new Semaphore(-ordersList.size() + 1); + for (List orders : ordersList) { + new Thread(() -> { + for (Order o : orders) + o.execute(); + semaphore.release(); + }).start(); + } + semaphore.acquireUninterruptibly(); + 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); + 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); + } + final Semaphore semaphore = new Semaphore(-ordersList.size() + 1); + for (List orders : ordersList) { + new Thread(() -> { + for (Order o : orders) + o.execute(); + semaphore.release(); + }).start(); + } + semaphore.acquireUninterruptibly(); + 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)); + 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); + } + final Semaphore semaphore = new Semaphore(-4); + for (int i = 0; i < 5; i++) { + new Thread(() -> { + while (true) { + DefaultEdge e; + if (edges.size() > 400) + e = edges.remove(0); + else { + semaphore.release(); + return; + } + g.removeEdge(e); + } + }).start(); + } + semaphore.acquireUninterruptibly(); + 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)); + 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); + final Semaphore semaphore = new Semaphore(-2); + for (int i = 0; i < 3; i++) { + new Thread(() -> { + while (true) { + int c; + if (vertices.size() > 10) + c = vertices.remove(0); + else { + semaphore.release(); + return; + } + g.removeVertex(c); + } + }).start(); + } + semaphore.acquireUninterruptibly(); + 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)); + 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.add(order1); + ordersList.add(order2); + ordersList.add(order3); + ordersList.add(order4); + Semaphore semaphore1 = new Semaphore(-ordersList.size() + 1); + for (List orders : ordersList) { + new Thread(() -> { + for (Order o : orders) + o.execute(); + semaphore1.release(); + }).start(); + } + semaphore1.acquireUninterruptibly(); + 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)); + ordersList.clear(); + ordersList.add(order1); + ordersList.add(order2); + ordersList.add(order3); + Semaphore semaphore2 = new Semaphore(-ordersList.size() + 1); + for (List orders : ordersList) { + new Thread(() -> { + for (Order o : orders) + o.execute(); + semaphore2.release(); + }).start(); + } + semaphore2.acquireUninterruptibly(); + 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)); + for (int i = 0; i < 1000; i++) { + g.addVertex(i); + vertices.add(i); + } + for (int i = 0; i < 1000; i++) { + DefaultEdge e = new DefaultEdge(); + g.addEdge(i, (i + 1) % 1000, e); + edges.add(e); + } + final Semaphore semaphore = new Semaphore(0, true); + new Thread(() -> { + while (true) { + int c; + if (vertices.size() > 10) + c = vertices.remove(0); + else { + semaphore.release(); + return; + } + g.getLock().readLock().lock(); + try { + for (DefaultEdge e : g.edgesOf(c)) { + assertTrue(g.containsEdge(e)); + } + } finally { + g.getLock().readLock().unlock(); + } + } + }, "verifyEdges").run(); + new Thread(() -> { + semaphore.acquireUninterruptibly(); + while (true) { + DefaultEdge e; + if (edges.size() > 400) + e = edges.remove(0); + else { + semaphore.release(); + return; + } + g.removeEdge(e); + } + }, "removeEdge").run(); + semaphore.acquireUninterruptibly(); + } + + 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)); + } + } + } + + @FunctionalInterface + 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; + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/interfaces/AStarAdmissibleHeuristicTest.java b/jgrapht-core/src/test/java/org/jgrapht/interfaces/AStarAdmissibleHeuristicTest.java new file mode 100644 index 00000000000..16d2591d57d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/interfaces/AStarAdmissibleHeuristicTest.java @@ -0,0 +1,116 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This class tests default implementation of the + * {@link org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic#isConsistent(Graph)} method. + */ +public class AStarAdmissibleHeuristicTest + extends BaseHeuristicSearchTest +{ + + @Test + public void testNullValue() + { + assertThrows(IllegalArgumentException.class, () -> { + 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)); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/clique/MaximalCliqueEnumerationPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/clique/MaximalCliqueEnumerationPerformanceTest.java new file mode 100644 index 00000000000..528f5558059 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/clique/MaximalCliqueEnumerationPerformanceTest.java @@ -0,0 +1,122 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +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(); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/connectivity/TreeDynamicConnectivityPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/connectivity/TreeDynamicConnectivityPerformanceTest.java new file mode 100644 index 00000000000..89aecb877e7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/connectivity/TreeDynamicConnectivityPerformanceTest.java @@ -0,0 +1,142 @@ +/* + * (C) Copyright 2020-2023, 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/flow/MaximumFlowAlgorithmPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/flow/MaximumFlowAlgorithmPerformanceTest.java new file mode 100644 index 00000000000..716b9a05daf --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/flow/MaximumFlowAlgorithmPerformanceTest.java @@ -0,0 +1,130 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; +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(); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphPerformanceTest.java new file mode 100644 index 00000000000..6fa87074da4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphPerformanceTest.java @@ -0,0 +1,223 @@ +/* + * (C) Copyright 2008-2023, 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.jupiter.api.*; +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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphVSStaticGraphPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphVSStaticGraphPerformanceTest.java new file mode 100644 index 00000000000..f7360512428 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/DirectedAcyclicGraphVSStaticGraphPerformanceTest.java @@ -0,0 +1,169 @@ +/* + * (C) Copyright 2008-2023, 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.jupiter.api.*; +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(); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/graph/GraphPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/GraphPerformanceTest.java new file mode 100644 index 00000000000..6b9615b3ea9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/graph/GraphPerformanceTest.java @@ -0,0 +1,236 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; +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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/lca/LowestCommonAncestorAlgorithmPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/lca/LowestCommonAncestorAlgorithmPerformanceTest.java new file mode 100644 index 00000000000..9047f510355 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/lca/LowestCommonAncestorAlgorithmPerformanceTest.java @@ -0,0 +1,255 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; +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(); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/matching/MaximumCardinalityBipartiteMatchingPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/MaximumCardinalityBipartiteMatchingPerformanceTest.java new file mode 100644 index 00000000000..39cb41eda01 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/MaximumCardinalityBipartiteMatchingPerformanceTest.java @@ -0,0 +1,129 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; +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(); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/matching/PathGrowingWeightedMatchingPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/PathGrowingWeightedMatchingPerformanceTest.java new file mode 100644 index 00000000000..c180c127051 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/PathGrowingWeightedMatchingPerformanceTest.java @@ -0,0 +1,136 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +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(); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/v5/KolmogorovMinimumWeightPerfectMatchingPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/v5/KolmogorovMinimumWeightPerfectMatchingPerformanceTest.java new file mode 100644 index 00000000000..7a5482f8f57 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/v5/KolmogorovMinimumWeightPerfectMatchingPerformanceTest.java @@ -0,0 +1,66 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DeltaSteppingShortestPathPerformance.java b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DeltaSteppingShortestPathPerformance.java new file mode 100644 index 00000000000..a2c3e2e258a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DeltaSteppingShortestPathPerformance.java @@ -0,0 +1,309 @@ +/* + * (C) Copyright 2018-2023, 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); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DijkstraShortestPathPerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DijkstraShortestPathPerformanceTest.java new file mode 100644 index 00000000000..260837bcfb3 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/DijkstraShortestPathPerformanceTest.java @@ -0,0 +1,345 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +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); + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/KShortestPathsPerformance.java b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/KShortestPathsPerformance.java new file mode 100644 index 00000000000..c2b639f8324 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/KShortestPathsPerformance.java @@ -0,0 +1,188 @@ +/* + * (C) Copyright 2018-2023, 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/perf/spanning/MinimumSpanningTreePerformanceTest.java b/jgrapht-core/src/test/java/org/jgrapht/perf/spanning/MinimumSpanningTreePerformanceTest.java new file mode 100644 index 00000000000..741913f6268 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/perf/spanning/MinimumSpanningTreePerformanceTest.java @@ -0,0 +1,268 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +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); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/AbstractGraphIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/AbstractGraphIteratorTest.java index dd8b9f6abf1..39f4ffe59ce 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/AbstractGraphIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/AbstractGraphIteratorTest.java @@ -1,80 +1,59 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Liviu Rau and Contributors. * - * 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. - */ -/* ------------------------------ - * AbstractGraphIteratorTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Liviu Rau and Contributors. - * - * Original Author: Liviu Rau - * Contributor(s): Barak Naveh + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 30-Jul-2003 : Initial revision (LR); - * 06-Aug-2003 : Test traversal listener & extract a shared superclass (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * A basis for testing {@link org.jgrapht.traverse.BreadthFirstIterator} and * {@link org.jgrapht.traverse.DepthFirstIterator} classes. * * @author Liviu Rau - * @since Jul 30, 2003 */ public abstract class AbstractGraphIteratorTest - extends EnhancedTestCase { - //~ Instance fields -------------------------------------------------------- - - StringBuffer result; + // ~ Instance fields -------------------------------------------------------- - //~ Methods ---------------------------------------------------------------- + StringBuilder result; /** * . */ + @Test public void testDirectedGraph() { - result = new StringBuffer(); - DirectedGraph graph = createDirectedGraph(); + Graph graph = createDirectedGraph(); + AbstractGraphIterator iterator = createIterator(graph, "1"); - AbstractGraphIterator iterator = - createIterator(graph, "1"); - MyTraversalListener listener = new MyTraversalListener(); - iterator.addTraversalListener(listener); + doDirectedGraphTest(iterator); + } + protected void collectResult(Iterator iterator, StringBuilder result) + { while (iterator.hasNext()) { result.append(iterator.next()); @@ -82,7 +61,17 @@ public void testDirectedGraph() 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()); @@ -97,11 +86,10 @@ String getExpectedFinishString() return ""; } - DirectedGraph createDirectedGraph() + Graph createDirectedGraph() { - DirectedGraph graph = - new DefaultDirectedWeightedGraph( - DefaultWeightedEdge.class); + Graph graph = + new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); // String v1 = "1"; @@ -126,9 +114,9 @@ DirectedGraph createDirectedGraph() 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. + // 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); @@ -145,19 +133,18 @@ DirectedGraph createDirectedGraph() return graph; } - abstract AbstractGraphIterator createIterator( - DirectedGraph g, - String startVertex); + abstract AbstractGraphIterator createIterator( + Graph g, String startVertex); - //~ Inner Classes ---------------------------------------------------------- + // ~ Inner Classes ---------------------------------------------------------- /** * Internal traversal listener. * * @author Barak Naveh */ - private class MyTraversalListener - implements TraversalListener + private class MyTraversalListener + implements TraversalListener { private int componentNumber = 0; private int numComponentVertices = 0; @@ -167,8 +154,8 @@ private class MyTraversalListener /** * @see TraversalListener#connectedComponentFinished(ConnectedComponentTraversalEvent) */ - public void connectedComponentFinished( - ConnectedComponentTraversalEvent e) + @Override + public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { switch (componentNumber) { case 1: @@ -184,7 +171,7 @@ public void connectedComponentFinished( break; default: - assertFalse(); + fail("Should not get here."); break; } @@ -195,8 +182,8 @@ public void connectedComponentFinished( /** * @see TraversalListener#connectedComponentStarted(ConnectedComponentTraversalEvent) */ - public void connectedComponentStarted( - ConnectedComponentTraversalEvent e) + @Override + public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { componentNumber++; } @@ -204,7 +191,8 @@ public void connectedComponentStarted( /** * @see TraversalListener#edgeTraversed(EdgeTraversalEvent) */ - public void edgeTraversed(EdgeTraversalEvent e) + @Override + public void edgeTraversed(EdgeTraversalEvent e) { // to be tested... } @@ -212,6 +200,7 @@ public void edgeTraversed(EdgeTraversalEvent e) /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ + @Override public void vertexTraversed(VertexTraversalEvent e) { numComponentVertices++; @@ -220,6 +209,7 @@ public void vertexTraversed(VertexTraversalEvent e) /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ + @Override public void vertexFinished(VertexTraversalEvent e) { finishString += e.getVertex() + ":"; @@ -231,5 +221,3 @@ public String getFinishString() } } } - -// End AbstractGraphIteratorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/AllTraverseTests.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/AllTraverseTests.java deleted file mode 100644 index 8987b569489..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/AllTraverseTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------- - * AllTraverseTests.java - * ---------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 24-Jul-2003 : Initial revision (BN); - * - */ -package org.jgrapht.traverse; - -import junit.framework.*; - - -/** - * A TestSuite for all tests in this package. - * - * @author Barak Naveh - */ -public final class AllTraverseTests -{ - //~ Constructors ----------------------------------------------------------- - - private AllTraverseTests() - { - } // ensure non-instantiability. - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates a test suite for all tests in this package. - * - * @return a test suite for all tests in this package. - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // $JUnit-BEGIN$ - suite.addTest(new TestSuite(BreadthFirstIteratorTest.class)); - suite.addTest(new TestSuite(DepthFirstIteratorTest.class)); - suite.addTest(new TestSuite(ClosestFirstIteratorTest.class)); - suite.addTest(new TestSuite(IgnoreDirectionTest.class)); - suite.addTest(new TestSuite(TopologicalOrderIteratorTest.class)); - - // $JUnit-END$ - return suite; - } -} - -// End AllTraverseTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/BreadthFirstIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/BreadthFirstIteratorTest.java index c8416fe9cbc..b7b640e97a7 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/BreadthFirstIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/BreadthFirstIteratorTest.java @@ -1,84 +1,162 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Liviu Rau and Contributors. * - * 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. - */ -/* ----------------------------- - * BreadthFirstIteratorTest.java - * ----------------------------- - * (C) Copyright 2003-2008, by Liviu Rau and Contributors. - * - * Original Author: Liviu Rau - * Contributor(s): Barak Naveh + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 30-Jul-2003 : Initial revision (LR); - * 06-Aug-2003 : Test traversal listener & extract a shared superclass (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.*; /** * 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.

    + *

    + * 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 - * @since Jul 30, 2003 + * @author Patrick Sharp */ public class BreadthFirstIteratorTest - extends AbstractGraphIteratorTest + extends CrossComponentIteratorTest { - //~ Methods ---------------------------------------------------------------- + // ~ 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"; } - AbstractGraphIterator createIterator( - DirectedGraph g, - String vertex) + @Override + AbstractGraphIterator createIterator( + Graph g, String vertex) { - AbstractGraphIterator i = - new BreadthFirstIterator(g, vertex); + AbstractGraphIterator i = + new BreadthFirstIterator<>(g, vertex); i.setCrossComponentTraversal(true); return i; } -} -// End BreadthFirstIteratorTest.java + @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(0, bfs.getParent(1)); + assertEquals(1, bfs.getParent(2)); + assertEquals(2, bfs.getParent(3)); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/ClosestFirstIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/ClosestFirstIteratorTest.java index e36bbfe9be7..6e33c180254 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/ClosestFirstIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/ClosestFirstIteratorTest.java @@ -1,129 +1,157 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* ----------------------------- - * ClosestFirstIteratorTest.java - * ----------------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 03-Sep-2003 : Initial revision (JVS); - * 29-May-2005 : Test radius support (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests for ClosestFirstIterator. * - * @author John V. Sichi - * @since Sep 3, 2003 + * @author John V. Sichi, Patrick Sharp */ public class ClosestFirstIteratorTest - extends AbstractGraphIteratorTest + extends CrossComponentIteratorTest { - //~ Methods ---------------------------------------------------------------- + // ~ Methods ---------------------------------------------------------------- /** * . */ + @Test public void testRadius() { - result = new StringBuffer(); + result = new StringBuilder(); - DirectedGraph graph = createDirectedGraph(); + Graph graph = createDirectedGraph(); - // NOTE: pick 301 as the radius because it discriminates + // NOTE: pick 301 as the radius because it discriminates // the boundary case edge between v7 and v9 - AbstractGraphIterator iterator = - new ClosestFirstIterator(graph, "1", 301); - - while (iterator.hasNext()) { - result.append(iterator.next()); - - if (iterator.hasNext()) { - result.append(','); - } - } + 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. */ - public void testNoStart() + @Test + public void testMultipleStarts() { - result = new StringBuffer(); - - DirectedGraph graph = createDirectedGraph(); - - AbstractGraphIterator iterator = - new ClosestFirstIterator(graph); - - while (iterator.hasNext()) { - result.append(iterator.next()); - - if (iterator.hasNext()) { - result.append(','); - } - } - - assertEquals("1,2,3,5,6,7,9,4,8,orphan", result.toString()); + 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 + // 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"; } - AbstractGraphIterator createIterator( - DirectedGraph g, - String vertex) + @Override + AbstractGraphIterator createIterator( + Graph g, String vertex) { - AbstractGraphIterator i = - new ClosestFirstIterator(g, vertex); + AbstractGraphIterator i = + new ClosestFirstIterator<>(g, vertex); i.setCrossComponentTraversal(true); return i; } -} -// End ClosestFirstIteratorTest.java + @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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/CrossComponentIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/CrossComponentIteratorTest.java new file mode 100644 index 00000000000..454e5d33c5b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/CrossComponentIteratorTest.java @@ -0,0 +1,225 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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(0); + final ModifiableInteger sizeCalls = new ModifiableInteger(0); + Graph graph = createDirectedGraph(); + Graph wrapper = new GraphDelegator<>(graph) + { + private static final long serialVersionUID = 1L; + + @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: + 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/DegeneracyOrderingIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/DegeneracyOrderingIteratorTest.java new file mode 100644 index 00000000000..8a8b5632220 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/DegeneracyOrderingIteratorTest.java @@ -0,0 +1,165 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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,v3,v4,v1,v2", 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_k,f_k,s_i,f_i,s_g,f_g,s_h,f_h,s_e,f_e,s_f,f_f", + 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(); + } + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/DepthFirstIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/DepthFirstIteratorTest.java index fc0c99435c1..a3f14cf8c4d 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/DepthFirstIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/DepthFirstIteratorTest.java @@ -1,100 +1,111 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------------- - * DepthFirstIteratorTest.java - * --------------------------- - * (C) Copyright 2003-2008, by Liviu Rau and Contributors. +/* + * (C) Copyright 2003-2023, by Liviu Rau and Contributors. * - * Original Author: Liviu Rau - * Contributor(s): Barak Naveh + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 30-Jul-2003 : Initial revision (LR); - * 06-Aug-2003 : Test traversal listener & extract a shared superclass (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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.

    + *

    + * 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 - * @since Jul 30, 2003 + * @author Liviu Rau, Patrick Sharp */ public class DepthFirstIteratorTest - extends AbstractGraphIteratorTest + extends CrossComponentIteratorTest { - //~ Methods ---------------------------------------------------------------- + // ~ 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:"; } - AbstractGraphIterator createIterator( - DirectedGraph g, - String vertex) + @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() { - AbstractGraphIterator i = - new DepthFirstIterator(g, vertex); + 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. + * See Sourceforge bug 1169182 for details. */ + @Test public void testBug1169182() { - DirectedGraph dg = - new DefaultDirectedGraph(DefaultEdge.class); + Graph dg = new DefaultDirectedGraph<>(DefaultEdge.class); String a = "A"; String b = "B"; @@ -138,7 +149,7 @@ public void testBug1169182() dg.addEdge(j, k); dg.addEdge(k, l); - Iterator dfs = new DepthFirstIterator(dg); + Iterator dfs = new DepthFirstIterator<>(dg); String actual = ""; while (dfs.hasNext()) { String v = dfs.next(); @@ -149,5 +160,3 @@ public void testBug1169182() assertEquals(expected, actual); } } - -// End DepthFirstIteratorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/EdgeSelectionTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/EdgeSelectionTest.java new file mode 100644 index 00000000000..a93c6b8d887 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/EdgeSelectionTest.java @@ -0,0 +1,148 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 static final long serialVersionUID = 1L; + 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/IgnoreDirectionTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/IgnoreDirectionTest.java index 1541c0da15d..71aebf2fa0f 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/IgnoreDirectionTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/IgnoreDirectionTest.java @@ -1,92 +1,70 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by John V Sichi and Contributors. * - * 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. - */ -/* --------------------------- - * IgnoreDirectionTest.java - * --------------------------- - * (C) Copyright 2003-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 08-Aug-2003 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.

    + *

    + * 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 - * @since Aug 8, 2003 */ public class IgnoreDirectionTest extends AbstractGraphIteratorTest { - //~ Methods ---------------------------------------------------------------- + // ~ 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:"; } - AbstractGraphIterator createIterator( - DirectedGraph g, - String vertex) + @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 - UndirectedGraph undirectedView = - new AsUndirectedGraph(g); - AbstractGraphIterator i = - new DepthFirstIterator(undirectedView, "4"); + Graph undirectedView = new AsUndirectedGraph<>(g); + AbstractGraphIterator i = + new DepthFirstIterator<>(undirectedView, "4"); i.setCrossComponentTraversal(true); return i; } } - -// End IgnoreDirectionTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/LexBreadthFirstIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/LexBreadthFirstIteratorTest.java new file mode 100644 index 00000000000..fd76c6befba --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/LexBreadthFirstIteratorTest.java @@ -0,0 +1,205 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testLexicographicalBfsIterator1() + { + assertThrows(NoSuchElementException.class, () -> { + 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()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/MaximumCardinalityIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/MaximumCardinalityIteratorTest.java new file mode 100644 index 00000000000..d495370d828 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/MaximumCardinalityIteratorTest.java @@ -0,0 +1,198 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testMaximumCardinalityIterator1() + { + assertThrows(NoSuchElementException.class, () -> { + 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()); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/RandomWalkVertexIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/RandomWalkVertexIteratorTest.java new file mode 100644 index 00000000000..d5ef15e9a7b --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/RandomWalkVertexIteratorTest.java @@ -0,0 +1,177 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.Test; + +/** + * Tests for the {@link RandomWalkVertexIterator} class. + * + * @author Assaf Mizrachi + * + */ +public class RandomWalkVertexIteratorTest +{ + + /** + * Tests invalid vertex + */ + @Test + public void testInvalidVertex() + { + assertThrows(IllegalArgumentException.class, () -> { + 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; + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/TopologicalOrderIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/TopologicalOrderIteratorTest.java index 8ac3b37c131..e313ee4d508 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/traverse/TopologicalOrderIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/TopologicalOrderIteratorTest.java @@ -1,71 +1,44 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* --------------------------- - * TopologicalOrderIteratorTest.java - * --------------------------- - * (C) Copyright 2005-2008, by John V. Sichi and Contributors. +/* + * (C) Copyright 2005-2023, by John V Sichi and Contributors. * - * Original Author: John V. Sichi - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 25-Apr-2005 : Initial revision (JVS); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.*; - import org.jgrapht.*; import org.jgrapht.graph.*; +import org.junit.jupiter.api.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests for TopologicalOrderIterator. * * @author John V. Sichi - * @since Apr 25, 2005 */ public class TopologicalOrderIteratorTest - extends EnhancedTestCase { - //~ Methods ---------------------------------------------------------------- - /** - * . - */ + @Test public void testRecipe() { - DirectedGraph graph = - new DefaultDirectedGraph( - DefaultEdge.class); + Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); - String [] v = new String[9]; + String[] v = new String[9]; v[0] = "preheat oven"; v[1] = "sift dry ingredients"; @@ -101,8 +74,7 @@ public void testRecipe() graph.addEdge(v[7], v[8]); graph.addEdge(v[6], v[8]); - Iterator iter = - new TopologicalOrderIterator(graph); + Iterator iter = new TopologicalOrderIterator<>(graph); int i = 0; while (iter.hasNext()) { @@ -111,10 +83,9 @@ public void testRecipe() } // Test with a reversed view - DirectedGraph reversed = - new EdgeReversedGraph(graph); + Graph reversed = new EdgeReversedGraph<>(graph); - iter = new TopologicalOrderIterator(reversed); + iter = new TopologicalOrderIterator<>(reversed); i = v.length - 1; while (iter.hasNext()) { @@ -123,18 +94,199 @@ public void testRecipe() } } - /** - * . - */ + @Test public void testEmptyGraph() { - DirectedGraph graph = - new DefaultDirectedGraph( - DefaultEdge.class); - Iterator iter = - new TopologicalOrderIterator(graph); + Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); + Iterator iter = new TopologicalOrderIterator<>(graph); assertFalse(iter.hasNext()); } -} -// End TopologicalOrderIteratorTest.java + @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 + public void testWithSelfLoops() + { + assertThrows(NotDirectedAcyclicGraphException.class, () -> { + 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 + public void testGraphWithCycle() + { + assertThrows(NotDirectedAcyclicGraphException.class, () -> { + 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(2, it.next()); + assertTrue(it.hasNext()); + assertEquals(3, it.next()); + assertTrue(it.hasNext()); + assertEquals(0, it.next()); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void testTryToDisableCrossComponent() + { + assertThrows(IllegalArgumentException.class, () -> { + Graph graph = new DirectedPseudograph<>(DefaultEdge.class); + new TopologicalOrderIterator<>(graph).setCrossComponentTraversal(false); + }); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/traverse/VertexTrackingTraversalListener.java b/jgrapht-core/src/test/java/org/jgrapht/traverse/VertexTrackingTraversalListener.java new file mode 100644 index 00000000000..41338d7cf17 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/traverse/VertexTrackingTraversalListener.java @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/AVLTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/AVLTreeTest.java new file mode 100644 index 00000000000..b584ed11840 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/AVLTreeTest.java @@ -0,0 +1,294 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.jgrapht.util.AVLTree.TreeNode; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } + } + + 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; + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/AllUtilTests.java b/jgrapht-core/src/test/java/org/jgrapht/util/AllUtilTests.java deleted file mode 100644 index 01da6f339da..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/util/AllUtilTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * AllUtilTests.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. - * - * Original Author: Assaf Lehr - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - */ -package org.jgrapht.util; - -import junit.framework.*; - -import org.jgrapht.experimental.equivalence.*; -import org.jgrapht.experimental.permutation.*; - - -public class AllUtilTests -{ - //~ Methods ---------------------------------------------------------------- - - public static Test suite() - { - TestSuite suite = new TestSuite("Test for org.jgrapht.util"); - - // $JUnit-BEGIN$ - suite.addTestSuite(FibonacciHeapTest.class); - suite.addTestSuite(PrefetchIteratorTest.class); - suite.addTestSuite(CompoundPermutationIterTest.class); - suite.addTestSuite(EquivalenceGroupCreatorTest.class); - - // $JUnit-END$ - return suite; - } -} - -// End AllUtilTests.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/DoublyLinkedListTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/DoublyLinkedListTest.java new file mode 100644 index 00000000000..c1b7d77771d --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/DoublyLinkedListTest.java @@ -0,0 +1,2005 @@ +/* + * (C) Copyright 2020-2023, 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.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.abort; + +/** + * Tests for {@link DoublyLinkedList}. + * + * @author Hannes Wellmann + * + */ +public class DoublyLinkedListTest +{ + private static final int MAX_LIST_SIZE = 8; + + 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), createDoublyLinkedList(elements) }; + } + return parameterSets; + } + + // ------------------------------------------------------------------------ + // test cases + + /** Test for {@link DoublyLinkedList#isEmpty()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testIsEmpty(int size, List expectedElements, DoublyLinkedList list) + { + assertThat(list.isEmpty(), is(equalTo(size == 0))); + } + + /** Test for {@link DoublyLinkedList#size()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testSize(int size, List expectedElements, DoublyLinkedList list) + { + assertThat(list.size(), is(equalTo(size))); + } + + /** Test for {@link DoublyLinkedList#clear()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testClear(int size, List expectedElements, DoublyLinkedList list) + { + + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeFirst_freeNode_nodeAddedToList(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeFirst_nodeInOtherList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createListNodeInOtherList(); + + list.addNodeFirst(node); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeFirst_nodeOfThisList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) + abort(); // skip for empty list + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = list.getNode(size / 2); + + list.addNodeFirst(node); + }); + } + + /** Test for {@link DoublyLinkedList#addNodeLast(DoublyLinkedList.ListNode)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeLast_freeNode_nodeAddedToList(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeLast_nodeInOtherList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createListNodeInOtherList(); + + list.addNodeLast(node); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeLast_nodeOfThisList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); // skip for empty list + } + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = list.getNode(size / 2); + + list.addNodeLast(node); + }); + } + + /** Test for {@link DoublyLinkedList#addNode(int, DoublyLinkedList.ListNode)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNode_freeNode_nodeAddedToList(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNode_nodeInOtherList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createListNodeInOtherList(); + + list.addNode(size / 2, node); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNode_nodeOfThisList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); // skip for empty list + } + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = list.getLastNode(); + + list.addNode(size / 2, node); + }); + } + + /** + * Test for + * {@link DoublyLinkedList#addNodeBefore(DoublyLinkedList.ListNode, DoublyLinkedList.ListNode)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeBefore_freeNodeBeforeNodeInList_nodeAddedToList(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeBefore_freeNodeBeforeNodeInOtherList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createFreeListNode("another"); + ListNode beforeNode = createListNodeInOtherList(); + + list.addNodeBefore(node, beforeNode); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeBefore_freeNodeBeforeFreeNode_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createFreeListNode("another"); + ListNode beforeNode = createFreeListNode("another"); + + list.addNodeBefore(node, beforeNode); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeBefore_nodeInOtherListBeforeNodeOfList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) + abort(); // skip for empty list + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = createListNodeInOtherList(); + ListNode beforeNode = list.getNode(size / 2); + + list.addNodeBefore(node, beforeNode); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddNodeBefore_nodeInThisListBeforeNodeOfList_IllegalArgumentException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + return; // skip for empty list + } + assertThrows(IllegalArgumentException.class, () -> { + ListNode node = list.getFirstNode(); + ListNode beforeNode = list.getNode(size / 2); + + list.addNodeBefore(node, beforeNode); + }); + } + + /** Test for {@link DoublyLinkedList#getFirstNode()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetFirstNode(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.getFirstNode()); + return; + } + + ListNode firstNode = list.getFirstNode(); + + assertThat(firstNode, is(sameInstance(list.getNode(0)))); + assertThat(firstNode.getValue(), is(sameInstance(expectedElements.get(0)))); + } + + /** Test for {@link DoublyLinkedList#getLastNode()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetLastNode(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.getLastNode()); + return; + } + + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetNode(int size, List expectedElements, DoublyLinkedList list) + { + for (int i = 0; i < size; i++) { + String expectedElement = expectedElements.get(i); + + ListNode node = list.getNode(i); + + assertThat(node.getValue(), is(sameInstance(expectedElement))); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetNode_indexSize_IndexOutOfBoundsException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IndexOutOfBoundsException.class, () -> list.getNode(size)); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetNode_indexNegative_IndexOutOfBoundsException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IndexOutOfBoundsException.class, () -> list.getNode(-1)); + } + + /** Test for {@link DoublyLinkedList#indexOfNode(DoublyLinkedList.ListNode)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testIndexOfNode_nodeInList_indexOfNode(int size, List expectedElements, DoublyLinkedList list) + { + 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))); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testIndexOfNode_nodeInOtherList_minusOne(int size, List expectedElements, DoublyLinkedList list) + { + ListNode node = createListNodeInOtherList(); + + int indexOfNode = list.indexOfNode(node); + + assertThat(indexOfNode, is(equalTo(-1))); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testIndexOfNode_nodeInNoList_minusOne(int size, List expectedElements, DoublyLinkedList list) + { + ListNode node = createFreeListNode("another"); + + int indexOfNode = list.indexOfNode(node); + + assertThat(indexOfNode, is(equalTo(-1))); + } + + /** Test for {@link DoublyLinkedList#containsNode(DoublyLinkedList.ListNode)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testContainsNode_nodeInList_true(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + return; + } + ListNode node = list.getNode(size / 3); + + boolean contains = list.containsNode(node); + + assertTrue(contains); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testContainsNode_nodeInOtherList_false(int size, List expectedElements, DoublyLinkedList list) + { + ListNode node = createListNodeInOtherList(); + + boolean contains = list.containsNode(node); + + assertFalse(contains); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testContainsNode_nodeInNoList_false(int size, List expectedElements, DoublyLinkedList list) + { + ListNode node = createFreeListNode("another"); + + boolean contains = list.containsNode(node); + + assertFalse(contains); + } + + /** + * Test for {@link DoublyLinkedList#removeNode(DoublyLinkedList.ListNode)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveNode_nodeInList_nodeRemoved(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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()); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveNode_nodeNotInList_listUnchanged(int size, List expectedElements, DoublyLinkedList list) + { + ListNode nodeNotInList = createListNodeInOtherList(); + + boolean removed = list.removeNode(nodeNotInList); + + assertFalse(removed); + assertSameContent(list, expectedElements); // ensure list did not change + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveNode_removeAllNodesInListFromFront_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + 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()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveNode_removeAllNodesInListFromEnd_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + 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()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveNode_removeAllNodesInListFromMiddle_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testNodeOf_elementInList_nodeOfElement(int size, List expectedElements, DoublyLinkedList list) + { + 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))); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testNodeOf_elementNotInList_null(int size, List expectedElements, DoublyLinkedList list) + { + + String otherElement = "another"; + + ListNode nodeOfElement = list.nodeOf(otherElement); + + assertThat(nodeOfElement, is(sameInstance(null))); + } + + /** Test for {@link DoublyLinkedList#lastNodeOf(Object)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testLastNodeOf(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementFirst_nonNullValue_valueAdded(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementFirst_nullValue_valueAdded(int size, List expectedElements, DoublyLinkedList list) + { // 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementLast(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementBeforeNode_sucessorInList_ElementAdded(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + ListNode nodeBefore = null; + assertThrows( + NullPointerException.class, () -> list.addElementBeforeNode(nodeBefore, "another")); + return; + } + + String another = "another"; + int i = (int) (size / 2.5); + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(i, another); + + ListNode nodeBefore = list.getNode(i); + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementBeforeNode_sucessorInOtherList_IllegalStateException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + String another = "another"; + + ListNode nodeBefore = createListNodeInOtherList(); + list.addElementBeforeNode(nodeBefore, another); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddElementBeforeNode_sucessorInNoList_IllegalStateException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalArgumentException.class, () -> { + String another = "another"; + + ListNode nodeBefore = createFreeListNode("another"); + list.addElementBeforeNode(nodeBefore, another); + }); + } + + // test List methods + + /** Test for {@link DoublyLinkedList#add(int, Object)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddInt_atIndex0(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddInt_inTheMiddle(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddInt_atIndexSize(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetInt(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveInt_atIndex0(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0)); + return; + } + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveInt_inTheMiddle(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(size / 2)); + return; + } + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveInt_atIndexSizeMinusOne(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(size - 1)); + return; + } + 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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddFirst(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(0, anotherString); + + list.addFirst(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#addLast(Object)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAddLast(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(size, anotherString); + + list.addLast(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#offerFirst(Object)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testOfferFirst(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(0, anotherString); + + list.offerFirst(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#offerLast(Object)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testOfferLast(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(size, anotherString); + + list.offerLast(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#removeFirst()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveFirst(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.removeFirst()); + return; + } + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveLast(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.removeLast()); + return; + } + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPollFirst(int size, List expectedElements, DoublyLinkedList list) + { + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPollLast(int size, List expectedElements, DoublyLinkedList list) + { + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetFirst(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.getFirst()); + return; + } + String first = list.getFirst(); + + assertThat(first, is(sameInstance(expectedElements.get(0)))); + assertSameContent(list, expectedElements); // ensure content did not change + } + + /** + * Test for {@link DoublyLinkedList#getLast()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testGetLast(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.getLast()); + return; + } + 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()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPeekFirst(int size, List expectedElements, DoublyLinkedList list) + { + 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()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPeekLast(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveFirstOccurrence(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemoveLastOccurrence(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testOffer(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(size, anotherString); + + list.offer(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#remove()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testRemove(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.remove()); + return; + } + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPoll(int size, List expectedElements, DoublyLinkedList list) + { + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testElement(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.element()); + return; + } + String first = list.element(); + + assertThat(first, is(sameInstance(expectedElements.get(0)))); + assertSameContent(list, expectedElements); // ensure content did not change + } + + /** + * Test for {@link DoublyLinkedList#peek()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPeek(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPush(int size, List expectedElements, DoublyLinkedList list) + { + String anotherString = "another"; + + List expectedList = new ArrayList<>(expectedElements); + expectedList.add(0, anotherString); + + list.push(anotherString); + + assertSameContent(list, expectedList); + } + + /** + * Test for {@link DoublyLinkedList#pop()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPop(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.pop()); + return; + } + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testInvert(int size, List expectedElements, DoublyLinkedList list) + { + list.invert(); + + List expected = new ArrayList<>(expectedElements); + Collections.reverse(expected); + + assertSameContent(list, expected); + } + + /** + * Test for {@link DoublyLinkedList#moveFrom(int, DoublyLinkedList)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testMoveFrom(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testAppend(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testPrepend(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testCircularIterator(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows(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)}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testReverseCircularIterator(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + assertThrows( + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testDescendingIterator(int size, List expectedElements, DoublyLinkedList list) + { + 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()}. + */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testIterator(int size, List expectedElements, DoublyLinkedList list) + { + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorE(int size, List expectedElements, DoublyLinkedList list) + { // test only if returned ListIterator starts expected position beginning. + if (size == 0) { + assertThrows(NoSuchElementException.class, () -> list.listIterator(null)); + return; + } + String 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorInt_indexInTheMiddle_iteratorAtCorrectIndex(int size, List expectedElements, DoublyLinkedList list) + { // 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()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorNext_iterateForwardTroughCompleteList_ListNodesInOrder(int size, List expectedElements, DoublyLinkedList list) + { // 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()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorPrevious_iterateBackwardTroughCompleteList_ListNodesInOrder(int size, List expectedElements, DoublyLinkedList list) + { // 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))); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorNextPrevious_forwardBackwardPattern_correctElements(int size, List expectedElements, DoublyLinkedList list) + { + 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))); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIterator_iterateBehindTail(int size, List expectedElements, DoublyLinkedList list) + { + ListNodeIterator iterator = list.listIterator(); + for (int i = 0; i < size; i++) { + iterator.next(); + } + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, () -> iterator.next()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIterator_iterateBeforeHead(int size, List expectedElements, DoublyLinkedList list) + { + ListNodeIterator iterator = list.listIterator(size); + for (int i = 0; i < size; i++) { + iterator.previous(); + } + assertFalse(iterator.hasPrevious()); + assertThrows(NoSuchElementException.class, () -> iterator.previous()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIterator_concurrentAdd_ConcurrentModificationException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(ConcurrentModificationException.class, () -> { + ListNodeIterator listIterator = list.listIterator(); + + list.add("another"); + + listIterator.next(); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIterator_concurrentRemove_ConcurrentModificationException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + + ListNodeIterator listIterator = list.listIterator(); + list.removeLast(); + + assertThrows(ConcurrentModificationException.class, () -> listIterator.next()); + } + + /** Test for {@link DoublyLinkedList.ListNodeIterator#remove()}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_clearListFromTheFront_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + 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()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_clearListFromTheMiddle_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + 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()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_clearListFromTheEnd_emptyList(int size, List expectedElements, DoublyLinkedList list) + { + 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()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_notMovedListIterator_IllegalStateException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalStateException.class, () -> list.listIterator().remove()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_removeTwiceAfterNext_IllegalStateException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + ListNodeIterator listIterator = list.listIterator(); + listIterator.next(); + listIterator.remove(); + + // check if the last-node of the iterator is cleared correctly + + assertThrows(IllegalStateException.class, () -> listIterator.remove()); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorRemove_removeAfterAdd_IllegalStateException(int size, List expectedElements, DoublyLinkedList list) + { + 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() + + assertThrows(IllegalStateException.class, () -> listIterator.remove()); + } + + /** Test for {@link DoublyLinkedList.ListNodeIterator#add(Object)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorAdd_addElementsAtFront_listWithAdditionalElements(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorAdd_addElementsInTheMiddle_listWithAdditionalElements(int size, List expectedElements, DoublyLinkedList list) + { + 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); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorAdd_addElementsAtEnd_listWithAdditionalElements(int size, List expectedElements, DoublyLinkedList list) + { + + 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); + } + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorAdd_addElementBeforeEnd_listWithAdditionalElements(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + + 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)}. */ + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_replaceElementInTheMiddle_listWithReplacedElement(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_replaceElementAtFront_listWithReplacedElement(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_replaceElementInAtEnd_listWithReplacedElement(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_setElementWithSubsequentRemove_listWithReplacedElement(int size, List expectedElements, DoublyLinkedList list) + { // 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_setTwice_listWithReplacedElement(int size, List expectedElements, DoublyLinkedList list) + { + + if (size == 0) { + abort(); + } + 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); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_NotMovedListIterator_IllegalstateException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalStateException.class, () -> { + ListNodeIterator listIterator = list.listIterator(); + listIterator.set("another"); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_setAfterAdd_IllegalstateException(int size, List expectedElements, DoublyLinkedList list) + { + assertThrows(IllegalStateException.class, () -> { + ListNodeIterator listIterator = list.listIterator(); + listIterator.add("another"); + listIterator.set("another"); + }); + } + + @ParameterizedTest(name = "List with size {0}") + @MethodSource("getListSizes") + public void testListIteratorSet_setAfterRemove_IllegalstateException(int size, List expectedElements, DoublyLinkedList list) + { + if (size == 0) { + abort(); + } + ListNodeIterator listIterator = list.listIterator(); + listIterator.next(); + listIterator.remove(); + assertThrows(IllegalStateException.class, () -> listIterator.set("another")); + } + + /** + * Tests that {@code DoublyLinkedList} implements both {@code java.util.List} and {@code java.util.Deque} interfaces. + * + * @since 1.5.3 + */ + @Test + public void testInheritance() { + DoublyLinkedList list = new DoublyLinkedList<>(); + assertInstanceOf(List.class, list); + assertInstanceOf(Deque.class, list); + } + + /** + * Tests for {@link DoublyLinkedList.ReversedDoublyLinkedListView}. + * + * @since 1.5.3 + */ + @DisplayName("Reversed view tests") + @Nested + public class ReversedDoublyLinkedListViewTest { + + private DoublyLinkedList list; + private DoublyLinkedList reversedList; + private List expected; + + @BeforeEach + public void setup() { + expected = List.of(0, 1, 2, 3, 4); + list = createDoublyLinkedList(expected); + reversedList = list.reversed(); + } + + @Test + public void testHead() { + ListNode revHead = reversedList.head(); + assertSame(reversedList, revHead.getList()); + assertEquals(4, revHead.getValue()); + } + + @Test + public void testTail() { + ListNode revTail = reversedList.tail(); + assertSame(reversedList, revTail.getList()); + assertEquals(0, revTail.getValue()); + } + + @Test + public void testEmpty() { + assertFalse(reversedList.isEmpty()); + assertTrue(new DoublyLinkedList().reversed().isEmpty()); + } + + @Test + public void testSize() { + assertEquals(5, reversedList.size()); + assertEquals(0, new DoublyLinkedList().reversed().size()); + } + + @Test + public void testModification() { + assertThrows(UnsupportedOperationException.class, () -> reversedList.clear()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addNode(0, createFreeListNode(5))); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addNodeFirst(createFreeListNode(5))); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addNodeLast(createFreeListNode(-1))); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addNodeBefore(createFreeListNode(5), reversedList.getNode(0))); + assertThrows(UnsupportedOperationException.class, () -> reversedList.removeNode(reversedList.getNode(0))); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addElementFirst(5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addElementLast(-1)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addElementBeforeNode(reversedList.getNode(0), 5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.add(0, 5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.remove(0)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addFirst(5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.addLast(-1)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.offerFirst(4)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.offerLast(3)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.removeFirst()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.removeLast()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.pollFirst()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.pollLast()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.removeFirstOccurrence(2)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.removeLastOccurrence(1)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.offer(5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.remove(1)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.poll()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.push(5)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.pop()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.invert()); + assertThrows(UnsupportedOperationException.class, () -> reversedList.moveFrom(0, list)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.append(list)); + assertThrows(UnsupportedOperationException.class, () -> reversedList.prepend(list)); + } + + @Test + public void testGets() { + for (int i = 0; i < expected.size(); i++) { + assertEquals(i, reversedList.getNode(reversedList.size() - (1 + i)).getValue()); + assertEquals(i, reversedList.get(reversedList.size() - (1 + i))); + } + } + + @Test + public void testNodeIterator() { + NodeIterator iterator = reversedList.iterator(); + int expected = 4; + while (iterator.hasNext()) { + ListNode node = iterator.nextNode(); + assertSame(reversedList, node.getList()); + assertEquals(expected--, node.getValue()); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + } + + @Test + public void testListNodeIterator() { + ListNodeIterator iterator = reversedList.listIterator(); + int expected = 4; + while (iterator.hasNext()) { + ListNode node = iterator.nextNode(); + assertSame(reversedList, node.getList()); + assertEquals(expected--, node.getValue()); + assertThrows(UnsupportedOperationException.class, () -> iterator.add(6)); + } + } + + @Test + public void testDescendingIterator() { + NodeIterator expectedIterator = list.iterator(); + NodeIterator revDescendingNodeIterator = reversedList.descendingIterator(); + + while (expectedIterator.hasNext()) { + assertEquals(expectedIterator.next(), revDescendingNodeIterator.next()); + assertEquals(expectedIterator.hasNext(), revDescendingNodeIterator.hasNext()); + } + } + } + + // utility methods + + 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}. */ + static ListNode createFreeListNode(E 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)))); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/FibonacciHeapTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/FibonacciHeapTest.java deleted file mode 100644 index 843da1935d7..00000000000 --- a/jgrapht-core/src/test/java/org/jgrapht/util/FibonacciHeapTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------- - * FibonacciHeapTest.java - * ----------------- - * (C) Copyright 2008-2008, by John V. Sichi and Contributors. - * - * Original Author: John V. Sichi - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 20-Apr-2008 : Initial revision (JVS); - */ -package org.jgrapht.util; - -import java.util.*; - -import junit.framework.*; - - -public class FibonacciHeapTest - extends TestCase -{ - //~ Methods ---------------------------------------------------------------- - - // in honor of sf.net bug #1845376 - public void testAddRemoveOne() - { - String s = "A"; - FibonacciHeapNode n = new FibonacciHeapNode(s); - FibonacciHeap h = new FibonacciHeap(); - assertTrue(h.isEmpty()); - h.insert(n, 1.0); - assertFalse(h.isEmpty()); - FibonacciHeapNode n2 = h.removeMin(); - assertEquals(s, n2.getData()); - assertTrue(h.isEmpty()); - } - - public void testGrowReplaceShrink() - { - Random r = new Random(); - int k = 10000; - String s = "A"; - double t = 0; - FibonacciHeap h = new FibonacciHeap(); - for (int i = 0; i < (k * 3); ++i) { - // during first two-thirds, insert - if (i < (k * 2)) { - double d = r.nextDouble(); - t += d; - FibonacciHeapNode n = new FibonacciHeapNode(s); - h.insert(n, d); - } - - // during last two-thirds, delete (so during middle - // third, we'll do both insert and delete, interleaved) - if (i >= k) { - FibonacciHeapNode n2 = h.removeMin(); - t -= n2.getKey(); - } - } - assertTrue(h.isEmpty()); - - // tally should come back down to zero, or thereabouts (due to roundoff) - assertEquals(0.0, t, 0.00001); - } -} - -// End FibonacciHeapTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/PrefetchIteratorTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/PrefetchIteratorTest.java index d1d1e12b871..d5a4a724760 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/util/PrefetchIteratorTest.java +++ b/jgrapht-core/src/test/java/org/jgrapht/util/PrefetchIteratorTest.java @@ -1,141 +1,110 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * 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. - */ -/* ----------------- - * PrefetchIteratorTest.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Assaf Lehr - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; -import java.util.*; +import org.junit.jupiter.api.*; -import junit.framework.*; +import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class PrefetchIteratorTest - extends TestCase { - //~ Methods ---------------------------------------------------------------- + // ~ Methods ---------------------------------------------------------------- + @Test public void testIteratorInterface() { - Iterator iterator = new IterateFrom1To99(); + Iterator iterator = new IterateFrom1To99(); for (int i = 1; i < 100; i++) { - assertEquals(true, iterator.hasNext()); + assertTrue(iterator.hasNext()); assertEquals(i, iterator.next()); } - assertEquals(false, iterator.hasNext()); - Exception exceptionThrown = null; - try { - iterator.next(); - } catch (Exception e) { - exceptionThrown = e; - } - assertTrue(exceptionThrown instanceof NoSuchElementException); + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, () -> iterator.next()); } + @Test public void testEnumInterface() { - Enumeration enumuration = new IterateFrom1To99(); + Enumeration enumuration = new IterateFrom1To99(); for (int i = 1; i < 100; i++) { - assertEquals(true, enumuration.hasMoreElements()); + assertTrue(enumuration.hasMoreElements()); assertEquals(i, enumuration.nextElement()); } - assertEquals(false, enumuration.hasMoreElements()); - Exception exceptionThrown = null; - try { - enumuration.nextElement(); - } catch (Exception e) { - exceptionThrown = e; - } - assertTrue(exceptionThrown instanceof NoSuchElementException); + assertFalse(enumuration.hasMoreElements()); + assertThrows(NoSuchElementException.class, ()-> enumuration.nextElement()); } - //~ Inner Classes ---------------------------------------------------------- + // ~ Inner Classes ---------------------------------------------------------- // This test class supplies enumeration of integer from 1 till 100. public static class IterateFrom1To99 - implements Enumeration, - Iterator + implements Enumeration, Iterator { private int counter = 0; - private PrefetchIterator nextSupplier; + private PrefetchIterator nextSupplier; public IterateFrom1To99() { - nextSupplier = - new PrefetchIterator( - new PrefetchIterator.NextElementFunctor() { - public Integer nextElement() - throws NoSuchElementException - { - counter++; - if (counter >= 100) { - throw new NoSuchElementException(); - } else { - return new Integer(counter); - } - } - }); + 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 - public Object nextElement() + @Override + public Integer nextElement() { return this.nextSupplier.nextElement(); } - public Object next() + @Override + public Integer next() { return this.nextSupplier.next(); } + @Override public boolean hasNext() { return this.nextSupplier.hasNext(); } + @Override public void remove() { this.nextSupplier.remove(); } } } - -// End PrefetchIteratorTest.java diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/RadixSortTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/RadixSortTest.java new file mode 100644 index 00000000000..7c4d0e991bf --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/RadixSortTest.java @@ -0,0 +1,128 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.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 + @Tag("slow") + public void testRandomArrays() + { + testRandomArrays(new Random(0x88)); + } + + @Test + @Tag("slow") + public void testRandomArraysWithNoFixedSeed() + { + testRandomArrays(new Random()); + } + + private void testRandomArrays(Random random) + { + final int numTests = 500_000; + + for (int test = 0; test < numTests; 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)); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/StopWatch.java b/jgrapht-core/src/test/java/org/jgrapht/util/StopWatch.java index 614cc3f1487..fa9291325f6 100644 --- a/jgrapht-core/src/test/java/org/jgrapht/util/StopWatch.java +++ b/jgrapht-core/src/test/java/org/jgrapht/util/StopWatch.java @@ -1,74 +1,58 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2005-2023, by Assaf Lehr and Contributors. * - * 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. - */ -/* ----------------- - * StopWatch.java - * ----------------- - * (C) Copyright 2005-2008, by Assaf Lehr and Contributors. + * JGraphT : a free Java graph-theory library * - * Original Author: Assaf Lehr - * Contributor(s): - + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * $Id$ + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * - * Changes - * ------- + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; +import java.util.concurrent.*; + /** - * @author Assaf - * @since May 30, 2005 + * A very simple stop watch. + * + * @author Assaf Lehr */ public class StopWatch { - //~ Instance fields -------------------------------------------------------- - - long beforeTime; + private long startTime; - //~ Methods ---------------------------------------------------------------- + /** + * Construct a new stop watch and start it. + */ + public StopWatch() + { + start(); + } + /** + * Restart. + */ public void start() { - this.beforeTime = System.currentTimeMillis(); + this.startTime = System.nanoTime(); } - public void stopAndReport() + /** + * 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) { - long deltaTime = System.currentTimeMillis() - beforeTime; - if (deltaTime > 9999) { - double deltaTimeSec = deltaTime / 1000.0; - System.out.println( - "# Performence: " + deltaTimeSec + " full Seconds"); - } else { - String timeDesc; - timeDesc = - (deltaTime <= 10) ? "<10ms [less than minumun measurement time]" - : String.valueOf(deltaTime); - System.out.println("# Performence: in MiliSeconds:" + timeDesc); - } + return timeUnit.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); } -} -// End StopWatch.java +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/SupplierUtilTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/SupplierUtilTest.java new file mode 100644 index 00000000000..2a639118be9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/SupplierUtilTest.java @@ -0,0 +1,184 @@ +/* + * (C) Copyright 2021-2023, 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.junit.jupiter.api.*; + +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.jupiter.api.Assertions.*; + +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); + 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(suppliedValues.add(value), "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(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(suppliedValues.add(value), "Equal value supplied multiple times"); + } + + 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)))); + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/UnmodifiableUnionSetTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/UnmodifiableUnionSetTest.java new file mode 100644 index 00000000000..b9630c7e4c2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/UnmodifiableUnionSetTest.java @@ -0,0 +1,307 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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(); + + @Override + public boolean hasNext() + { + return delegateIterator.hasNext(); + } + + @Override + 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; + } + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/util/VertexToIntegerMappingTest.java b/jgrapht-core/src/test/java/org/jgrapht/util/VertexToIntegerMappingTest.java new file mode 100644 index 00000000000..ee60cbb34c7 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/util/VertexToIntegerMappingTest.java @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for {@link VertexToIntegerMapping} + * + * @author Alexandru Valeanu + */ +public class VertexToIntegerMappingTest +{ + + @Test + public void testNullSet() + { + assertThrows(NullPointerException.class, () -> new VertexToIntegerMapping<>((Set) null)); + } + + @Test + public void testEmptySet() + { + VertexToIntegerMapping mapping = new VertexToIntegerMapping<>(new HashSet<>()); + + assertTrue(mapping.getIndexList().isEmpty()); + assertTrue(mapping.getVertexMap().isEmpty()); + } + + @Test + public void testNotUniqueElements() + { + assertThrows(IllegalArgumentException.class, () -> new VertexToIntegerMapping<>(Arrays.asList(1, 2, 1))); + } + + @Test + public void testRandomInstances() + { + Random random = new Random(0x88); + final int numTests = 1024; + Supplier supplier = SupplierUtil.createStringSupplier(random.nextInt(100)); + + for (int test = 0; test < numTests; 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(); + + assertEquals(n, vertexMap.size()); + assertEquals(n, indexList.size()); + + for (int i = 0; i < indexList.size(); i++) { + assertEquals(i, vertexMap.get(indexList.get(i))); + } + + for (Map.Entry entry : vertexMap.entrySet()) { + assertEquals(indexList.get(entry.getValue()), entry.getKey()); + } + } + } +} diff --git a/jgrapht-core/src/test/resources/edges.txt b/jgrapht-core/src/test/resources/edges.txt new file mode 100644 index 00000000000..476c4dd82fb --- /dev/null +++ b/jgrapht-core/src/test/resources/edges.txt @@ -0,0 +1,1772 @@ +M050 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 diff --git a/jgrapht-demo/pom.xml b/jgrapht-demo/pom.xml index 19806875050..fef41c0fe7c 100644 --- a/jgrapht-demo/pom.xml +++ b/jgrapht-demo/pom.xml @@ -1,51 +1,57 @@ - - 4.0.0 - - net.sf.jgrapht - jgrapht - 0.8.3-SNAPSHOT - - jgrapht-demo - JGraphT - Demo - - - - org.apache.maven.plugins - maven-jar-plugin - 2.3.2 - - - - org.jgrapht.demo.JGraphAdapterDemo - true - - - - - - org.apache.maven.plugins - maven-shade-plugin - 1.5 - - - package - - shade - - - - - ${project.artifactId}-${project.version}-shade - - - - - - - ${project.groupId} - jgrapht-ext - - + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + org.junit.jupiter + junit-jupiter + test + + diff --git a/jgrapht-demo/src/main/java/module-info.java b/jgrapht-demo/src/main/java/module-info.java new file mode 100644 index 00000000000..1e7d9985147 --- /dev/null +++ b/jgrapht-demo/src/main/java/module-info.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides demo applications of the JGraphT library. + * + * @since 1.5.0 + */ +module 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; +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java old mode 100755 new mode 100644 index 39544896ddf..5e6765863b3 --- a/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java @@ -1,128 +1,86 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Tim Shearouse and Contributors. * - * 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. - */ -/* -------------------- - * CompleteGraphDemo.java - * -------------------- - * (C) Copyright 2003-2008, by Tim Shearouse and Contributors. - * - * Original Author: Tim Shearouse - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.util.*; +//@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 { - //~ Static fields/initializers --------------------------------------------- - - static Graph completeGraph; - - //Number of vertices - static int size = 10; - - //~ Methods ---------------------------------------------------------------- - - public static void main(String [] args) + // 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 graph object; it is null at this point - completeGraph = new SimpleGraph(DefaultEdge.class); + // Create the VertexFactory so the generator can create vertices + Supplier vSupplier = new Supplier() + { + private int id = 0; - //Create the CompleteGraphGenerator object - CompleteGraphGenerator completeGenerator = - new CompleteGraphGenerator(size); + @Override + public String get() + { + return "v" + id++; + } + }; - //Create the VertexFactory so the generator can create vertices - VertexFactory vFactory = - new ClassBasedVertexFactory(Object.class); + // @example:generate:begin + // Create the graph object + Graph completeGraph = + new SimpleGraph<>(vSupplier, SupplierUtil.createDefaultEdgeSupplier(), false); - //Use the CompleteGraphGenerator object to make completeGraph a - //complete graph with [size] number of vertices - completeGenerator.generateGraph(completeGraph, vFactory, null); + // Create the CompleteGraphGenerator object + CompleteGraphGenerator completeGenerator = + new CompleteGraphGenerator<>(SIZE); - //Now, replace all the vertices with sequential numbers so we can ID - //them - Set vertices = new HashSet(); - vertices.addAll(completeGraph.vertexSet()); - Integer counter = 0; - for (Object vertex : vertices) { - replaceVertex(vertex, (Object) counter++); - } + // 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); - Object vertex; + // Print out the graph to be sure it's really complete + Iterator iter = new DepthFirstIterator<>(completeGraph); while (iter.hasNext()) { - vertex = iter.next(); + String vertex = iter.next(); System.out.println( - "Vertex " + vertex.toString() + " is connected to: " - + completeGraph.edgesOf(vertex).toString()); - } - } - - public static boolean replaceVertex(Object oldVertex, Object newVertex) - { - if ((oldVertex == null) || (newVertex == null)) { - return false; + "Vertex " + vertex + " is connected to: " + + completeGraph.edgesOf(vertex).toString()); } - Set relatedEdges = completeGraph.edgesOf(oldVertex); - completeGraph.addVertex(newVertex); - - Object sourceVertex; - Object targetVertex; - for (DefaultEdge e : relatedEdges) { - sourceVertex = completeGraph.getEdgeSource(e); - targetVertex = completeGraph.getEdgeTarget(e); - if (sourceVertex.equals(oldVertex) - && targetVertex.equals(oldVertex)) - { - completeGraph.addEdge(newVertex, newVertex); - } else { - if (sourceVertex.equals(oldVertex)) { - completeGraph.addEdge(newVertex, targetVertex); - } else { - completeGraph.addEdge(sourceVertex, newVertex); - } - } - } - completeGraph.removeVertex(oldVertex); - return true; } } - -// End CompleteGraphDemo.java +// @example:class:end diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/DependencyDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/DependencyDemo.java new file mode 100644 index 00000000000..ac4256811a3 --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/DependencyDemo.java @@ -0,0 +1,134 @@ +/* + * (C) Copyright 2012-2023, 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); + } +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/DirectedGraphDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/DirectedGraphDemo.java new file mode 100644 index 00000000000..328eb4a881a --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/DirectedGraphDemo.java @@ -0,0 +1,106 @@ +/* + * (C) Copyright 2008-2023, 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 + } +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java new file mode 100644 index 00000000000..c280455f5fc --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java @@ -0,0 +1,68 @@ +/* + * (C) Copyright 2018-2023, 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 + +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphMLDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphMLDemo.java new file mode 100644 index 00000000000..5baddea3e90 --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphMLDemo.java @@ -0,0 +1,311 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + + } + +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java index ea3365c796b..90e51a98a92 100644 --- a/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java @@ -1,125 +1,176 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* ----------------- - * HelloJGraphT.java - * ----------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 27-Jul-2003 : Initial revision (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.net.*; +//@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 - * @since Jul 27, 2003 */ public final class HelloJGraphT { - //~ Constructors ----------------------------------------------------------- - private HelloJGraphT() { } // ensure non-instantiability. - //~ Methods ---------------------------------------------------------------- - /** * 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) + public static void main(String[] args) + throws URISyntaxException, ExportException { - UndirectedGraph stringGraph = createStringGraph(); + 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 URL objects - DirectedGraph hrefGraph = createHrefGraph(); + // create a graph based on URI objects + Graph hrefGraph = createHrefGraph(); - // note directed edges are printed as: (,) - System.out.println(hrefGraph.toString()); + // 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 URL objects that represents link - * structure. + * Creates a toy directed graph based on URI objects that represents link structure. * - * @return a graph based on URL objects. + * @return a graph based on URI objects. */ - private static DirectedGraph createHrefGraph() + private static Graph createHrefGraph() + throws URISyntaxException { - DirectedGraph g = - new DefaultDirectedGraph(DefaultEdge.class); - - try { - URL amazon = new URL("http://www.amazon.com"); - URL yahoo = new URL("http://www.yahoo.com"); - URL ebay = new URL("http://www.ebay.com"); - - // add the vertices - g.addVertex(amazon); - g.addVertex(yahoo); - g.addVertex(ebay); - - // add edges to create linking structure - g.addEdge(yahoo, amazon); - g.addEdge(yahoo, ebay); - } catch (MalformedURLException e) { - e.printStackTrace(); - } + // @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; } /** - * Craete a toy graph based on String objects. + * 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 UndirectedGraph createStringGraph() + private static Graph createStringGraph() { - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); + Graph g = new SimpleGraph<>(DefaultEdge.class); String v1 = "v1"; String v2 = "v2"; @@ -141,5 +192,3 @@ private static UndirectedGraph createStringGraph() return g; } } - -// End HelloJGraphT.java diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphAdapterDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphAdapterDemo.java deleted file mode 100644 index 90eff794edd..00000000000 --- a/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphAdapterDemo.java +++ /dev/null @@ -1,203 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ---------------------- - * JGraphAdapterDemo.java - * ---------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 03-Aug-2003 : Initial revision (BN); - * 07-Nov-2003 : Adaptation to JGraph 3.0 (BN); - * - */ -package org.jgrapht.demo; - -import java.awt.*; -import java.awt.geom.*; - -import javax.swing.*; - -import org.jgraph.*; -import org.jgraph.graph.*; - -import org.jgrapht.*; -import org.jgrapht.ext.*; -import org.jgrapht.graph.*; - -// resolve ambiguity -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 -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3256444702936019250L; - private static final Color DEFAULT_BG_COLOR = Color.decode("#FAFBFF"); - private static final Dimension DEFAULT_SIZE = new Dimension(530, 320); - - //~ Instance fields -------------------------------------------------------- - - // - private JGraphModelAdapter jgAdapter; - - //~ Methods ---------------------------------------------------------------- - - /** - * An alternative starting point for this demo, to also allow running this - * applet as an application. - * - * @param args ignored. - */ - public static void main(String [] args) - { - JGraphAdapterDemo applet = new JGraphAdapterDemo(); - applet.init(); - - JFrame frame = new JFrame(); - frame.getContentPane().add(applet); - frame.setTitle("JGraphT Adapter to JGraph Demo"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.pack(); - frame.setVisible(true); - } - - /** - * {@inheritDoc} - */ - public void init() - { - // create a JGraphT graph - ListenableGraph g = - new ListenableDirectedMultigraph( - DefaultEdge.class); - - // create a visualization using JGraph, via an adapter - jgAdapter = new JGraphModelAdapter(g); - - JGraph jgraph = new JGraph(jgAdapter); - - adjustDisplaySettings(jgraph); - getContentPane().add(jgraph); - resize(DEFAULT_SIZE); - - String v1 = "v1"; - String v2 = "v2"; - String v3 = "v3"; - String v4 = "v4"; - - // 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); - } - - @SuppressWarnings("unchecked") // FIXME hb 28-nov-05: See FIXME below - private void positionVertexAt(Object vertex, int x, int y) - { - DefaultGraphCell cell = jgAdapter.getVertexCell(vertex); - AttributeMap attr = cell.getAttributes(); - Rectangle2D bounds = GraphConstants.getBounds(attr); - - Rectangle2D newBounds = - new Rectangle2D.Double( - x, - y, - bounds.getWidth(), - bounds.getHeight()); - - GraphConstants.setBounds(attr, newBounds); - - // TODO: Clean up generics once JGraph goes generic - AttributeMap cellAttr = new AttributeMap(); - cellAttr.put(cell, attr); - jgAdapter.edit(cellAttr, null, null, null); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * a listenable directed multigraph that allows loops and parallel edges. - */ - private static class ListenableDirectedMultigraph - extends DefaultListenableGraph - implements DirectedGraph - { - private static final long serialVersionUID = 1L; - - ListenableDirectedMultigraph(Class edgeClass) - { - super(new DirectedMultigraph(edgeClass)); - } - } -} - -// End JGraphAdapterDemo.java diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphXAdapterDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphXAdapterDemo.java new file mode 100644 index 00000000000..ce0ce459855 --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphXAdapterDemo.java @@ -0,0 +1,111 @@ +/* + * (C) Copyright 2013-2023, 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 diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java new file mode 100644 index 00000000000..a0ba0cbdd54 --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java @@ -0,0 +1,131 @@ +/* + * (C) Copyright 2012-2023, 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 diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/ParberryKnightTour.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/ParberryKnightTour.java new file mode 100644 index 00000000000..749d7fd5d96 --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/ParberryKnightTour.java @@ -0,0 +1,361 @@ +/* + * (C) Copyright 2018-2023, 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)); + } +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/PerformanceDemo.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/PerformanceDemo.java index 79cff854214..2a69e27d9f1 100644 --- a/jgrapht-demo/src/main/java/org/jgrapht/demo/PerformanceDemo.java +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/PerformanceDemo.java @@ -1,86 +1,59 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. +/* + * (C) Copyright 2003-2023, by Barak Naveh and Contributors. * - * 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. - */ -/* -------------------- - * PerformanceDemo.java - * -------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): - + * JGraphT : a free Java graph-theory library * - * $Id$ + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. * - * Changes - * ------- - * 10-Aug-2003 : Initial revision (BN); + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 java.io.*; - -import java.util.*; - 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. + * 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.

    + *

    + * 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.

    + *

    + * 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 - * @since Aug 10, 2003 */ public final class PerformanceDemo { - //~ Methods ---------------------------------------------------------------- - /** * The starting point for the demo. * * @param args ignored. */ - public static void main(String [] args) + public static void main(String[] args) { long time = System.currentTimeMillis(); reportPerformanceFor("starting at", time); - Graph g = - new Pseudograph(DefaultEdge.class); + Graph g = new Pseudograph<>(DefaultEdge.class); Object prev; Object curr; @@ -93,7 +66,7 @@ public static void main(String [] args) System.out.println( "\n" + "allocating graph with " + numElements - + " elements (may take a few tens of seconds)..."); + + " elements (may take a few tens of seconds)..."); for (int i = 0; i < numVertices; i++) { curr = new Object(); @@ -110,11 +83,7 @@ public static void main(String [] args) time = System.currentTimeMillis(); - for ( - Iterator i = - new BreadthFirstIterator(g); - i.hasNext();) - { + for (Iterator i = new BreadthFirstIterator<>(g); i.hasNext();) { i.next(); } @@ -122,18 +91,13 @@ public static void main(String [] args) time = System.currentTimeMillis(); - for ( - Iterator i = new DepthFirstIterator(g); - i.hasNext();) - { + 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.println("\n" + "Paused: graph is still in memory (to check mem consumption)."); System.out.print("press enter to free memory and finish..."); try { @@ -148,8 +112,7 @@ public static void main(String [] args) private static void reportPerformanceFor(String msg, long refTime) { double time = (System.currentTimeMillis() - refTime) / 1000.0; - double mem = usedMemory() - / (1024.0 * 1024.0); + double mem = usedMemory() / (1024.0 * 1024.0); mem = Math.round(mem * 100) / 100.0; System.out.println(msg + " (" + time + " sec, " + mem + "MB)"); } @@ -161,5 +124,3 @@ private static long usedMemory() return rt.totalMemory() - rt.freeMemory(); } } - -// End PerformanceDemo.java diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristic.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristic.java new file mode 100644 index 00000000000..b0d91182a5f --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristic.java @@ -0,0 +1,834 @@ +/* + * (C) Copyright 2018-2023, 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; + } +} diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/package-info.java b/jgrapht-demo/src/main/java/org/jgrapht/demo/package-info.java new file mode 100644 index 00000000000..5086bf8712c --- /dev/null +++ b/jgrapht-demo/src/main/java/org/jgrapht/demo/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Demo programs that help to get started with JGraphT. + */ +package org.jgrapht.demo; diff --git a/jgrapht-demo/src/main/java/org/jgrapht/demo/package.html b/jgrapht-demo/src/main/java/org/jgrapht/demo/package.html deleted file mode 100644 index f488b0db70f..00000000000 --- a/jgrapht-demo/src/main/java/org/jgrapht/demo/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -Demo programs that help to get started with JGraphT. - - \ No newline at end of file diff --git a/jgrapht-demo/src/test/java/org/jgrapht/demo/ParberryKnightTourTest.java b/jgrapht-demo/src/test/java/org/jgrapht/demo/ParberryKnightTourTest.java new file mode 100644 index 00000000000..b283dfa650a --- /dev/null +++ b/jgrapht-demo/src/test/java/org/jgrapht/demo/ParberryKnightTourTest.java @@ -0,0 +1,257 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.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 + public void testParberryIncorrectBoardConf1() + { + assertThrows(IllegalArgumentException.class, () -> new ParberryKnightTour(2, 2)); + } + + @Test + public void testParberryIncorrectBoardConf2() + { + assertThrows(IllegalArgumentException.class, () -> new ParberryKnightTour(21, 22)); + } + + @Test + public void testParberryIncorrectBoardConf3() + { + assertThrows(IllegalArgumentException.class, () -> new ParberryKnightTour(73, 73)); + } + + @Test + public void testParberryIncorrectBoardConf4() + { + assertThrows(IllegalArgumentException.class, () -> new ParberryKnightTour(-20, 20)); + } + + @Test + public void testParberryIncorrectBoardConf5() + { + assertThrows(IllegalArgumentException.class, () -> 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()); + } +} diff --git a/jgrapht-demo/src/test/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristicTest.java b/jgrapht-demo/src/test/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristicTest.java new file mode 100644 index 00000000000..bfa299d0c2a --- /dev/null +++ b/jgrapht-demo/src/test/java/org/jgrapht/demo/WarnsdorffRuleKnightTourHeuristicTest.java @@ -0,0 +1,380 @@ +/* + * (C) Copyright 2018-2023, 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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class WarnsdorffRuleKnightTourHeuristicTest +{ + + private KnightTour container; + private WarnsdorffRuleKnightTourHeuristic solver; + + @BeforeEach + 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 + public void warnsdorffIncorrectConfigurationOpen() + { + assertThrows(IllegalArgumentException.class, () -> { + solver = new WarnsdorffRuleKnightTourHeuristic(2, 200); + container = solver.getTour(TourType.OPEN, false, 0, 0); + }); + } + + @Test + public void warnsdorffIncorrectConfigurationOpen3x5() + { + assertThrows(IllegalArgumentException.class, () -> { + solver = new WarnsdorffRuleKnightTourHeuristic(5, 3); + container = solver.getTour(TourType.OPEN, false, 0, 0); + }); + } + + @Test + public void warnsdorffIncorrectConfigurationOpen3x6() + { + assertThrows(IllegalArgumentException.class, () -> { + solver = new WarnsdorffRuleKnightTourHeuristic(3, 6); + container = solver.getTour(TourType.OPEN, false, 0, 0); + }); + } + + @Test + public void warnsdorffIncorrectConfigurationOpen4x4() + { + assertThrows(IllegalArgumentException.class, () -> { + solver = new WarnsdorffRuleKnightTourHeuristic(4, 4); + container = solver.getTour(TourType.OPEN, false, 0, 0); + }); + } + + @Test + public void warnsdorffIncorrectTourConfigurationClosedBothOdd() + { + assertThrows(IllegalArgumentException.class, () -> { + solver = new WarnsdorffRuleKnightTourHeuristic(31, 31); + container = solver.getTour(TourType.CLOSED, false, 0, 0); + }); + } + + @Test + public void warnsdorffIncorrectTourConfigurationClosed4x6() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void warnsdorffIncorrectBoardConfiguration0x100() + { + assertThrows(IllegalArgumentException.class, () -> new WarnsdorffRuleKnightTourHeuristic(0, 100)); + } + + @Test + public void warnsdorffIncorrectBoardConfigurationBothNegative() + { + assertThrows(IllegalArgumentException.class, () -> new WarnsdorffRuleKnightTourHeuristic(-132)); + } + + @Test + public void warnsdorffIncorrectBoardConfigurationBothNegative2() + { + assertThrows(IllegalArgumentException.class, () -> new WarnsdorffRuleKnightTourHeuristic(-132, -140)); + } + + @Test + public void warnsdorffIncorrectBoardConfigurationOneDimSmall() + { + assertThrows(IllegalArgumentException.class, () -> 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()); + } +} diff --git a/jgrapht-dist/pom.xml b/jgrapht-dist/pom.xml index c389de100a5..86714c35a92 100644 --- a/jgrapht-dist/pom.xml +++ b/jgrapht-dist/pom.xml @@ -1,67 +1,103 @@ - - 4.0.0 - - net.sf.jgrapht - jgrapht - 0.8.3-SNAPSHOT - - jgrapht-dist - JGraphT - Distributable Archives - pom - - - - ${project.groupId} - jgrapht-core - - - ${project.groupId} - jgrapht-ext - - - ${project.groupId} - jgrapht-ext - combined - ${project.version} - - - ${project.groupId} - jgrapht-demo - - - - - - - maven-assembly-plugin - - - package - - single - - - true - jgrapht-${project.version} - ${basedir}/src/assembly - - - - - - - - - - touchgraph - - - ${project.groupId} - jgrapht-touchgraph - ${project.version} - - - - + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + + diff --git a/jgrapht-dist/src/assembly/assembly.xml b/jgrapht-dist/src/assembly/assembly.xml index 2eb8470a596..8f00398454a 100644 --- a/jgrapht-dist/src/assembly/assembly.xml +++ b/jgrapht-dist/src/assembly/assembly.xml @@ -1,62 +1,142 @@ - - zip - tar.gz - - - - - false - /lib - runtime - - - net.sf.jgrapht:jgrapht-ext:*:combined - - - - - false - /lib - runtime - - jgrapht-${artifact.version}${dashClassifier?}.${artifact.extension} - - - net.sf.jgrapht:jgrapht-ext:*:combined - - - - - - - ${basedir}/../README.md - / - - - ${basedir}/../HISTORY.md - / - - - ${basedir}/../CONTRIBUTORS.md - / - - - ${basedir}/../license-LGPL.txt - / - - + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> + assembly + + zip + tar.gz + - - - ${basedir}/.. - /source - - **/target/** - - - + + + 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/reports/apidocs + javadoc + + **/* + + + diff --git a/jgrapht-ext/pom.xml b/jgrapht-ext/pom.xml index f6eb10ce451..71621206350 100644 --- a/jgrapht-ext/pom.xml +++ b/jgrapht-ext/pom.xml @@ -1,58 +1,62 @@ - - 4.0.0 - - net.sf.jgrapht - jgrapht - 0.8.3-SNAPSHOT - - jgrapht-ext - JGraphT - Ext - - - - org.apache.maven.plugins - maven-shade-plugin - 1.5 - - - package - - shade - - - - - - - net.sf.jgrapht - - - true - combined - jgrapht-${project.version}-combined - - - - - - - ${project.groupId} - jgrapht-core - - - jgraph - jgraph - 5.13.0.0 - - - xmlunit - xmlunit - - - junit - junit - - + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + org.junit.jupiter + junit-jupiter + test + + diff --git a/jgrapht-ext/src/main/java/module-info.java b/jgrapht-ext/src/main/java/module-info.java new file mode 100644 index 00000000000..76970420fc0 --- /dev/null +++ b/jgrapht-ext/src/main/java/module-info.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides adaptors for the JGraphT graphs to be used + * with external libraries. + * + * @since 1.5.0 + */ +module org.jgrapht.ext +{ + exports org.jgrapht.ext; + + requires transitive org.jgrapht.core; + requires transitive com.github.vlsi.mxgraph.jgraphx; +} diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/ComponentAttributeProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/ComponentAttributeProvider.java deleted file mode 100644 index 8467816ffef..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/ComponentAttributeProvider.java +++ /dev/null @@ -1,66 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * ComponentAttributeProvider.java - * ------------------ - * (C) Copyright 2010-2010, by John Sichi and Contributors. - * - * Original Author: John Sichi - * - * $Id$ - * - * Changes - * ------- - * 12-Jun-2010 : Initial Version (JVS); - * - */ -package org.jgrapht.ext; - -import java.util.*; - - -/** - * Provides display attributes for vertices and/or edges in a graph. - * - * @author John Sichi - * @version $Id$ - */ -public interface ComponentAttributeProvider -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Returns a set of attribute key/value pairs for a vertex or edge. If order - * is important in the output, be sure to use an order-deterministic map - * implementation. - * - * @param component vertex or edge for which attributes are to be obtained - * - * @return key/value pairs, or null if no attributes should be supplied - */ - public Map getComponentAttributes(T component); -} - -// End ComponentAttributeProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/DOTExporter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/DOTExporter.java deleted file mode 100644 index a0fe41ba006..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/DOTExporter.java +++ /dev/null @@ -1,260 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * DOTExporter.java - * ------------------ - * (C) Copyright 2006, by Trevor Harmon. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * Exports a graph into a DOT file. - * - *

    For a description of the format see - * http://en.wikipedia.org/wiki/DOT_language.

    - * - * @author Trevor Harmon - */ -public class DOTExporter -{ - //~ Instance fields -------------------------------------------------------- - - private VertexNameProvider vertexIDProvider; - private VertexNameProvider vertexLabelProvider; - private EdgeNameProvider edgeLabelProvider; - private ComponentAttributeProvider vertexAttributeProvider; - private ComponentAttributeProvider edgeAttributeProvider; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructs a new DOTExporter object with an integer name provider for the - * vertex IDs and null providers for the vertex and edge labels. - */ - public DOTExporter() - { - this(new IntegerNameProvider(), null, null); - } - - /** - * Constructs a new DOTExporter object with the given ID and label - * providers. - * - * @param vertexIDProvider for generating vertex IDs. Must not be null. - * @param vertexLabelProvider for generating vertex labels. If null, vertex - * labels will not be written to the file. - * @param edgeLabelProvider for generating edge labels. If null, edge labels - * will not be written to the file. - */ - public DOTExporter( - VertexNameProvider vertexIDProvider, - VertexNameProvider vertexLabelProvider, - EdgeNameProvider edgeLabelProvider) - { - this( - vertexIDProvider, - vertexLabelProvider, - edgeLabelProvider, - null, - null); - } - - /** - * Constructs a new DOTExporter object with the given ID, label, and - * attribute providers. Note that if a label provider conflicts with a - * label-supplying attribute provider, the label provider is given - * precedence. - * - * @param vertexIDProvider for generating vertex IDs. Must not be null. - * @param vertexLabelProvider for generating vertex labels. If null, vertex - * labels will not be written to the file (unless an attribute provider is - * supplied which also supplies labels). - * @param edgeLabelProvider for generating edge labels. If null, edge labels - * will not be written to the file. - * @param vertexAttributeProvider for generating vertex attributes. If null, - * vertex attributes will not be written to the file. - * @param edgeAttributeProvider for generating edge attributes. If null, - * edge attributes will not be written to the file. - */ - public DOTExporter( - VertexNameProvider vertexIDProvider, - VertexNameProvider vertexLabelProvider, - EdgeNameProvider edgeLabelProvider, - ComponentAttributeProvider vertexAttributeProvider, - ComponentAttributeProvider edgeAttributeProvider) - { - this.vertexIDProvider = vertexIDProvider; - this.vertexLabelProvider = vertexLabelProvider; - this.edgeLabelProvider = edgeLabelProvider; - this.vertexAttributeProvider = vertexAttributeProvider; - this.edgeAttributeProvider = edgeAttributeProvider; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Exports a graph into a plain text file in DOT format. - * - * @param writer the writer to which the graph to be exported - * @param g the graph to be exported - */ - public void export(Writer writer, Graph g) - { - PrintWriter out = new PrintWriter(writer); - String indent = " "; - String connector; - - if (g instanceof DirectedGraph) { - out.println("digraph G {"); - connector = " -> "; - } else { - out.println("graph G {"); - connector = " -- "; - } - - for (V v : g.vertexSet()) { - out.print(indent + getVertexID(v)); - - String labelName = null; - if (vertexLabelProvider != null) { - labelName = vertexLabelProvider.getVertexName(v); - } - Map attributes = null; - if (vertexAttributeProvider != null) { - attributes = vertexAttributeProvider.getComponentAttributes(v); - } - renderAttributes(out, labelName, attributes); - - out.println(";"); - } - - for (E e : g.edgeSet()) { - String source = getVertexID(g.getEdgeSource(e)); - String target = getVertexID(g.getEdgeTarget(e)); - - out.print(indent + source + connector + target); - - String labelName = null; - if (edgeLabelProvider != null) { - labelName = edgeLabelProvider.getEdgeName(e); - } - Map attributes = null; - if (edgeAttributeProvider != null) { - attributes = edgeAttributeProvider.getComponentAttributes(e); - } - renderAttributes(out, labelName, attributes); - - out.println(";"); - } - - out.println("}"); - - out.flush(); - } - - private void renderAttributes( - PrintWriter out, - String labelName, - Map attributes) - { - if ((labelName == null) && (attributes == null)) { - return; - } - out.print(" [ "); - if ((labelName == null) && (attributes != null)) { - labelName = attributes.get("label"); - } - if (labelName != null) { - out.print("label=\"" + labelName + "\" "); - } - if (attributes != null) { - for (Map.Entry entry : attributes.entrySet()) { - String name = entry.getKey(); - if (name.equals("label")) { - // already handled by special case above - continue; - } - out.print(name + "=\"" + entry.getValue() + "\" "); - } - } - out.print("]"); - } - - /** - * 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 RuntimeException if the given vertexIDProvider - * didn't generate a valid vertex ID. - */ - private String getVertexID(V v) - { - // TODO jvs 28-Jun-2008: possible optimizations here are - // (a) only validate once per vertex - // (b) compile regex patterns - - // use the associated id provider for an ID of the given vertex - String idCandidate = vertexIDProvider.getVertexName(v); - - // now test that this is a valid ID - boolean isAlphaDig = idCandidate.matches("[a-zA-Z]+([\\w_]*)?"); - boolean isDoubleQuoted = idCandidate.matches("\".*\""); - boolean isDotNumber = - idCandidate.matches("[-]?([.][0-9]+|[0-9]+([.][0-9]*)?)"); - boolean isHTML = idCandidate.matches("<.*>"); - - if (isAlphaDig || isDotNumber || isDoubleQuoted || isHTML) { - return idCandidate; - } - - throw new RuntimeException( - "Generated id '" + idCandidate + "'for vertex '" + v - + "' is not valid with respect to the .dot language"); - } -} - -// End DOTExporter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/EdgeNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/EdgeNameProvider.java deleted file mode 100644 index 05df894b5f5..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/EdgeNameProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * VertexNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Trevor Harmon. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -/** - * Assigns a display name for each of the graph edes. - */ -public interface EdgeNameProvider -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Returns a unique name for an edge. This is useful when exporting a graph, - * as it ensures that all edges are assigned simple, consistent names. - * - * @param edge the edge to be named - * - * @return the name of the edge - */ - public String getEdgeName(E edge); -} - -// End EdgeNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/GmlExporter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/GmlExporter.java deleted file mode 100644 index d7171d9b1e8..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/GmlExporter.java +++ /dev/null @@ -1,286 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * GmlExporter.java - * ------------------ - * (C) Copyright 2006, by Dimitrios Michail. - * - * Original Author: Dimitrios Michail - * - * $Id$ - * - * Changes - * ------- - * 15-Dec-2006 : Initial Version (DM); - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import org.jgrapht.*; - - -/** - * Exports a graph into a GML file (Graph Modelling Language). - * - *

    For a description of the format see - * http://www.infosun.fmi.uni-passau.de/Graphlet/GML/.

    - * - *

    The objects associated with vertices and edges are exported as labels - * using their toString() implementation. See the {@link - * #setPrintLabels(Integer)} method. The default behavior is to export no label - * information.

    - * - * @author Dimitrios Michail - */ -public class GmlExporter -{ - //~ Static fields/initializers --------------------------------------------- - - 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"; - - // TODO jvs 27-Jan-2008: convert these to enum - - /** - * Option to export no vertex or edge labels. - */ - public static final Integer PRINT_NO_LABELS = 1; - - /** - * Option to export only the edge labels. - */ - public static final Integer PRINT_EDGE_LABELS = 2; - - /** - * Option to export both edge and vertex labels. - */ - public static final Integer PRINT_EDGE_VERTEX_LABELS = 3; - - /** - * Option to export only the vertex labels. - */ - public static final Integer PRINT_VERTEX_LABELS = 4; - - //~ Instance fields -------------------------------------------------------- - - private Integer printLabels = PRINT_NO_LABELS; - - private VertexNameProvider vertexIDProvider; - private VertexNameProvider vertexLabelProvider; - private EdgeNameProvider edgeIDProvider; - private EdgeNameProvider edgeLabelProvider; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new GmlExporter object with integer name providers for the - * vertex and edge IDs and null providers for the vertex and edge labels. - */ - public GmlExporter() - { - this( - new IntegerNameProvider(), - null, - new IntegerEdgeNameProvider(), - null); - } - - /** - * Constructs a new GmlExporter object with the given ID and label - * providers. - * - * @param vertexIDProvider for generating vertex IDs. Must not be null. - * @param vertexLabelProvider for generating vertex labels. If null, vertex - * labels will be generated using the toString() method of the vertex - * object. - * @param edgeIDProvider for generating vertex IDs. Must not be null. - * @param edgeLabelProvider for generating edge labels. If null, edge labels - * will be generated using the toString() method of the edge object. - */ - public GmlExporter( - VertexNameProvider vertexIDProvider, - VertexNameProvider vertexLabelProvider, - EdgeNameProvider edgeIDProvider, - EdgeNameProvider edgeLabelProvider) - { - this.vertexIDProvider = vertexIDProvider; - this.vertexLabelProvider = vertexLabelProvider; - this.edgeIDProvider = edgeIDProvider; - this.edgeLabelProvider = edgeLabelProvider; - } - - //~ Methods ---------------------------------------------------------------- - - private String quoted(final String s) - { - return "\"" + s + "\""; - } - - private void exportHeader(PrintWriter out) - { - out.println("Creator" + delim + quoted(creator)); - out.println("Version" + delim + version); - } - - private void exportVertices( - PrintWriter out, - Graph g) - { - for (V from : g.vertexSet()) { - out.println(tab1 + "node"); - out.println(tab1 + "["); - out.println( - tab2 + "id" + delim + vertexIDProvider.getVertexName(from)); - if ((printLabels == PRINT_VERTEX_LABELS) - || (printLabels == PRINT_EDGE_VERTEX_LABELS)) - { - String label = - (vertexLabelProvider == null) ? from.toString() - : vertexLabelProvider.getVertexName(from); - out.println(tab2 + "label" + delim + quoted(label)); - } - out.println(tab1 + "]"); - } - } - - private void exportEdges( - PrintWriter out, - Graph g) - { - for (E edge : g.edgeSet()) { - out.println(tab1 + "edge"); - out.println(tab1 + "["); - String id = edgeIDProvider.getEdgeName(edge); - out.println(tab2 + "id" + delim + id); - String s = vertexIDProvider.getVertexName(g.getEdgeSource(edge)); - out.println(tab2 + "source" + delim + s); - String t = vertexIDProvider.getVertexName(g.getEdgeTarget(edge)); - out.println(tab2 + "target" + delim + t); - if ((printLabels == PRINT_EDGE_LABELS) - || (printLabels == PRINT_EDGE_VERTEX_LABELS)) - { - String label = - (edgeLabelProvider == null) ? edge.toString() - : edgeLabelProvider.getEdgeName(edge); - out.println(tab2 + "label" + delim + quoted(label)); - } - out.println(tab1 + "]"); - } - } - - private void export(Writer output, Graph g, boolean directed) - { - PrintWriter out = new PrintWriter(output); - - for (V from : g.vertexSet()) { - // assign ids in vertex set iteration order - vertexIDProvider.getVertexName(from); - } - - exportHeader(out); - out.println("graph"); - out.println("["); - out.println(tab1 + "label" + delim + quoted("")); - if (directed) { - out.println(tab1 + "directed" + delim + "1"); - } else { - out.println(tab1 + "directed" + delim + "0"); - } - exportVertices(out, g); - exportEdges(out, g); - out.println("]"); - out.flush(); - } - - /** - * Exports an undirected graph into a plain text file in GML format. - * - * @param output the writer to which the graph to be exported - * @param g the undirected graph to be exported - */ - public void export(Writer output, UndirectedGraph g) - { - export(output, g, false); - } - - /** - * Exports a directed graph into a plain text file in GML format. - * - * @param output the writer to which the graph to be exported - * @param g the directed graph to be exported - */ - public void export(Writer output, DirectedGraph g) - { - export(output, g, true); - } - - /** - * Set whether to export the vertex and edge labels. The default behavior is - * to export no vertex or edge labels. - * - * @param i What labels to export. Valid options are {@link - * #PRINT_NO_LABELS}, {@link #PRINT_EDGE_LABELS}, {@link - * #PRINT_EDGE_VERTEX_LABELS}, and {@link #PRINT_VERTEX_LABELS}. - * - * @throws IllegalArgumentException if a non-supported value is used - * - * @see #PRINT_NO_LABELS - * @see #PRINT_EDGE_LABELS - * @see #PRINT_EDGE_VERTEX_LABELS - * @see #PRINT_VERTEX_LABELS - */ - public void setPrintLabels(final Integer i) - { - if ((i != PRINT_NO_LABELS) - && (i != PRINT_EDGE_LABELS) - && (i != PRINT_EDGE_VERTEX_LABELS) - && (i != PRINT_VERTEX_LABELS)) - { - throw new IllegalArgumentException( - "Non-supported parameter value: " + Integer.toString(i)); - } - printLabels = i; - } - - /** - * Get whether to export the vertex and edge labels. - * - * @return One of the {@link #PRINT_NO_LABELS}, {@link #PRINT_EDGE_LABELS}, - * {@link #PRINT_EDGE_VERTEX_LABELS}, or {@link #PRINT_VERTEX_LABELS}. - */ - public Integer getPrintLabels() - { - return printLabels; - } -} - -// End GmlExporter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/GraphMLExporter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/GraphMLExporter.java deleted file mode 100644 index 1abae9fd172..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/GraphMLExporter.java +++ /dev/null @@ -1,260 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * GraphMLExporter.java - * ------------------ - * (C) Copyright 2006, by Trevor Harmon. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import javax.xml.transform.*; -import javax.xml.transform.sax.*; -import javax.xml.transform.stream.*; - -import org.jgrapht.*; - -import org.xml.sax.*; -import org.xml.sax.helpers.*; - - -/** - * Exports a graph into a GraphML file. - * - *

    For a description of the format see - * http://en.wikipedia.org/wiki/GraphML.

    - * - * @author Trevor Harmon - */ -public class GraphMLExporter -{ - //~ Instance fields -------------------------------------------------------- - - private VertexNameProvider vertexIDProvider; - private VertexNameProvider vertexLabelProvider; - private EdgeNameProvider edgeIDProvider; - private EdgeNameProvider edgeLabelProvider; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructs a new GraphMLExporter object with integer name providers for - * the vertex and edge IDs and null providers for the vertex and edge - * labels. - */ - public GraphMLExporter() - { - this( - new IntegerNameProvider(), - null, - new IntegerEdgeNameProvider(), - null); - } - - /** - * Constructs a new GraphMLExporter object with the given ID and label - * providers. - * - * @param vertexIDProvider for generating vertex IDs. Must not be null. - * @param vertexLabelProvider for generating vertex labels. If null, vertex - * labels will not be written to the file. - * @param edgeIDProvider for generating vertex IDs. Must not be null. - * @param edgeLabelProvider for generating edge labels. If null, edge labels - * will not be written to the file. - */ - public GraphMLExporter( - VertexNameProvider vertexIDProvider, - VertexNameProvider vertexLabelProvider, - EdgeNameProvider edgeIDProvider, - EdgeNameProvider edgeLabelProvider) - { - this.vertexIDProvider = vertexIDProvider; - this.vertexLabelProvider = vertexLabelProvider; - this.edgeIDProvider = edgeIDProvider; - this.edgeLabelProvider = edgeLabelProvider; - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Exports a graph into a plain text file in GraphML format. - * - * @param writer the writer to which the graph to be exported - * @param g the graph to be exported - */ - public void export(Writer writer, Graph g) - throws SAXException, TransformerConfigurationException - { - // Prepare an XML file to receive the GraphML data - PrintWriter out = new PrintWriter(writer); - StreamResult streamResult = new StreamResult(out); - SAXTransformerFactory factory = - (SAXTransformerFactory) SAXTransformerFactory.newInstance(); - TransformerHandler handler = factory.newTransformerHandler(); - Transformer serializer = handler.getTransformer(); - serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - serializer.setOutputProperty(OutputKeys.INDENT, "yes"); - handler.setResult(streamResult); - handler.startDocument(); - AttributesImpl attr = new AttributesImpl(); - - // - handler.startPrefixMapping( - "xsi", - "http://www.w3.org/2001/XMLSchema-instance"); - - // FIXME: Is this the proper way to add this attribute? - 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); - handler.endPrefixMapping("xsi"); - - if (vertexLabelProvider != null) { - // for vertex label attribute - attr.clear(); - attr.addAttribute("", "", "id", "CDATA", "vertex_label"); - attr.addAttribute("", "", "for", "CDATA", "node"); - attr.addAttribute("", "", "attr.name", "CDATA", "Vertex Label"); - attr.addAttribute("", "", "attr.type", "CDATA", "string"); - handler.startElement("", "", "key", attr); - handler.endElement("", "", "key"); - } - - if (edgeLabelProvider != null) { - // for edge label attribute - attr.clear(); - attr.addAttribute("", "", "id", "CDATA", "edge_label"); - attr.addAttribute("", "", "for", "CDATA", "edge"); - attr.addAttribute("", "", "attr.name", "CDATA", "Edge Label"); - attr.addAttribute("", "", "attr.type", "CDATA", "string"); - handler.startElement("", "", "key", attr); - handler.endElement("", "", "key"); - } - - // - attr.clear(); - attr.addAttribute( - "", - "", - "edgedefault", - "CDATA", - (g instanceof DirectedGraph) ? "directed" : "undirected"); - handler.startElement("", "", "graph", attr); - - // Add all the vertices as elements... - for (V v : g.vertexSet()) { - // - attr.clear(); - attr.addAttribute( - "", - "", - "id", - "CDATA", - vertexIDProvider.getVertexName(v)); - handler.startElement("", "", "node", attr); - - if (vertexLabelProvider != null) { - // - attr.clear(); - attr.addAttribute("", "", "key", "CDATA", "vertex_label"); - handler.startElement("", "", "data", attr); - - // Content for - String vertexLabel = vertexLabelProvider.getVertexName(v); - handler.characters( - vertexLabel.toCharArray(), - 0, - vertexLabel.length()); - - handler.endElement("", "", "data"); - } - - handler.endElement("", "", "node"); - } - - // Add all the edges as elements... - for (E e : g.edgeSet()) { - // - attr.clear(); - attr.addAttribute( - "", - "", - "id", - "CDATA", - edgeIDProvider.getEdgeName(e)); - attr.addAttribute( - "", - "", - "source", - "CDATA", - vertexIDProvider.getVertexName(g.getEdgeSource(e))); - attr.addAttribute( - "", - "", - "target", - "CDATA", - vertexIDProvider.getVertexName(g.getEdgeTarget(e))); - handler.startElement("", "", "edge", attr); - - if (edgeLabelProvider != null) { - // - attr.clear(); - attr.addAttribute("", "", "key", "CDATA", "edge_label"); - handler.startElement("", "", "data", attr); - - // Content for - String edgeLabel = edgeLabelProvider.getEdgeName(e); - handler.characters( - edgeLabel.toCharArray(), - 0, - edgeLabel.length()); - handler.endElement("", "", "data"); - } - - handler.endElement("", "", "edge"); - } - - handler.endElement("", "", "graph"); - handler.endElement("", "", "graphml"); - handler.endDocument(); - - out.flush(); - } -} - -// End GraphMLExporter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerEdgeNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerEdgeNameProvider.java deleted file mode 100644 index bf5f1d0f55c..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerEdgeNameProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * IntegerNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Trevor Harmon. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -import java.util.*; - - -/** - * Assigns a unique integer to represent each edge. Each instance of - * IntegerEdgeNameProvider maintains an internal map between every edge it has - * ever seen and the unique integer representing that edge. As a result it is - * probably desirable to have a separate instance for each distinct graph. - * - * @author Trevor Harmon - */ -public class IntegerEdgeNameProvider - implements EdgeNameProvider -{ - //~ Instance fields -------------------------------------------------------- - - private int nextID = 1; - private final Map idMap = new HashMap(); - - //~ Methods ---------------------------------------------------------------- - - /** - * Clears all cached identifiers, and resets the unique identifier counter. - */ - public void clear() - { - nextID = 1; - idMap.clear(); - } - - /** - * Returns the String representation of an edge. - * - * @param edge the edge to be named - */ - public String getEdgeName(E edge) - { - Integer id = idMap.get(edge); - if (id == null) { - id = nextID++; - idMap.put(edge, id); - } - - return id.toString(); - } -} - -// End IntegerEdgeNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerNameProvider.java deleted file mode 100644 index 840d3ec98a0..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/IntegerNameProvider.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * IntegerNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Charles Fry and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 13-Dec-2005 : Initial Version (CF); - * - */ -package org.jgrapht.ext; - -import java.util.*; - -import org.jgrapht.event.*; - - -/** - * Assigns a unique integer to represent each vertex. Each instance of - * IntegerNameProvider maintains an internal map between every vertex it has - * ever seen and the unique integer representing that vertex. As a result it is - * probably desirable to have a separate instance for each distinct graph. - * - * @author Charles Fry - */ -public class IntegerNameProvider - implements VertexNameProvider -{ - //~ Instance fields -------------------------------------------------------- - - private int nextID = 1; - private final Map idMap = new HashMap(); - - //~ Methods ---------------------------------------------------------------- - - /** - * Clears all cached identifiers, and resets the unique identifier counter. - */ - public void clear() - { - nextID = 1; - idMap.clear(); - } - - /** - * Returns the String representation of the unique integer representing a - * vertex. - * - * @param vertex the vertex to be named - * - * @return the name of - * - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public String getVertexName(V vertex) - { - Integer id = idMap.get(vertex); - if (id == null) { - id = nextID++; - idMap.put(vertex, id); - } - - return id.toString(); - } -} - -// End IntegerNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphModelAdapter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphModelAdapter.java deleted file mode 100644 index 2b6407a82e1..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphModelAdapter.java +++ /dev/null @@ -1,1143 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ----------------------- - * JGraphModelAdapter.java - * ----------------------- - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Barak Naveh - * Contributor(s): Erik Postma - * - * $Id$ - * - * Changes - * ------- - * 02-Aug-2003 : Initial revision (BN); - * 10-Aug-2003 : Adaptation to new event model (BN); - * 06-Nov-2003 : Allowed non-listenable underlying JGraphT graph (BN); - * 12-Dec-2003 : Added CellFactory support (BN); - * 27-Jan-2004 : Added support for JGraph->JGraphT change propagation (EP); - * 29-Jan-2005 : Added support for JGraph dangling edges (BN); - * 07-May-2006 : Changed from List to Set (JVS); - * 28-May-2006 : Moved connectivity info from edge to graph (JVS); - * - */ -package org.jgrapht.ext; - -import java.awt.Color; -import java.awt.Font; -import java.awt.geom.*; - -import java.io.*; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.swing.*; - -import org.jgraph.event.*; -import org.jgraph.event.GraphModelEvent.*; -import org.jgraph.graph.*; - -import org.jgrapht.*; -import org.jgrapht.event.*; - - -/** - * An adapter that reflects a JGraphT graph as a JGraph graph. This adapter is - * useful when using JGraph in order to visualize JGraphT graphs. For more about - * JGraph see - * http://jgraph.sourceforge.net - * - *

    Modifications made to the underlying JGraphT graph are reflected to this - * JGraph model if and only if the underlying JGraphT graph is a {@link - * org.jgrapht.ListenableGraph}. If the underlying JGraphT graph is not - * ListenableGraph, then this JGraph model represent a snapshot if the graph at - * the time of its creation.

    - * - *

    Changes made to this JGraph model are also reflected back to the - * underlying JGraphT graph. To avoid confusion, variables are prefixed - * according to the JGraph/JGraphT object(s) they are referring to.

    - * - *

    KNOWN BUGS: There is a small issue to be aware of. JGraph allows - * 'dangling edges' incident with just one vertex; JGraphT doesn't. Such a - * configuration can arise when adding an edge or removing a vertex. The code - * handles this by removing the newly-added dangling edge or removing all edges - * incident with the vertex before actually removing the vertex, respectively. - * This works very well, only it doesn't play all that nicely with the - * undo-manager in the JGraph: for the second situation where you remove a - * vertex incident with some edges, if you undo the removal, the vertex is - * 'unremoved' but the edges aren't.

    - * - * @author Barak Naveh - * @since Aug 2, 2003 - */ - -/* - * FUTURE WORK: Now that the adapter supports JGraph dangling edges, it is - * possible, with a little effort, to eliminate the "known bugs" above. Some - * todo and fixme marks in the code indicate where the possible improvements - * could be made to realize that. - */ -public class JGraphModelAdapter - extends DefaultGraphModel -{ - //~ Static fields/initializers --------------------------------------------- - - private static final long serialVersionUID = 3256722883706302515L; - - //~ Instance fields -------------------------------------------------------- - - /** - * The following (jCells|jtElement)Being(Added|Removed) sets are used to - * prevent bouncing of events between the JGraph and JGraphT listeners. They - * ensure that their respective add/remove operations are done exactly once. - * Here is an example of how jCellsBeingAdded is used when an edge is added - * to a JGraph graph: - * - *
    -        1. First, we add the desired edge to jCellsBeingAdded to indicate
    -        that the edge is being inserted internally.
    -        2.    Then we invoke the JGraph 'insert' operation.
    -        3.    The JGraph listener will detect the newly inserted edge.
    -        4.    It checks if the edge is contained in jCellsBeingAdded.
    -        5.    If yes,
    -        it just removes it and does nothing else.
    -        if no,
    -        it knows that the edge was inserted externally and performs
    -        the insertion.
    -        6. Lastly, we remove the edge from the jCellsBeingAdded.
    -     * 
    - * - *

    Step 6 is not always required but we do it anyway as a safeguard - * against the rare case where the edge to be added is already contained in - * the graph and thus NO event will be fired. If 6 is not done, a junk edge - * will remain in the jCellsBeingAdded set.

    - * - *

    The other sets are used in a similar manner to the above. Apparently, - * All that complication could be eliminated if JGraph and JGraphT had both - * allowed operations that do not inform listeners...

    - */ - final Set jCellsBeingAdded = new HashSet(); - final Set jCellsBeingRemoved = new HashSet(); - final Set jtElementsBeingAdded = new HashSet(); - final Set jtElementsBeingRemoved = new HashSet(); - private final CellFactory cellFactory; - - /** - * Maps JGraph edges to JGraphT edges - */ - private final Map cellToEdge = - new HashMap(); - - /** - * Maps JGraph vertices to JGraphT vertices - */ - private final Map cellToVertex = new HashMap(); - private AttributeMap defaultEdgeAttributes; - private AttributeMap defaultVertexAttributes; - - /** - * Maps JGraphT edges to JGraph edges - */ - private final Map edgeToCell = - new HashMap(); - - /** - * Maps JGraphT vertices to JGraph vertices - */ - private final Map vertexToCell = new HashMap(); - private final ShieldedGraph jtGraph; - - //~ Constructors ----------------------------------------------------------- - - /** - * Constructs a new JGraph model adapter for the specified JGraphT graph. - * - * @param jGraphTGraph the JGraphT graph for which JGraph model adapter to - * be created. null is NOT permitted. - */ - public JGraphModelAdapter(Graph jGraphTGraph) - { - this( - jGraphTGraph, - createDefaultVertexAttributes(), - createDefaultEdgeAttributes(jGraphTGraph)); - } - - /** - * Constructs a new JGraph model adapter for the specified JGraphT graph. - * - * @param jGraphTGraph the JGraphT graph for which JGraph model adapter to - * be created. null is NOT permitted. - * @param defaultVertexAttributes a default map of JGraph attributes to - * format vertices. null is NOT permitted. - * @param defaultEdgeAttributes a default map of JGraph attributes to format - * edges. null is NOT permitted. - */ - public JGraphModelAdapter( - Graph jGraphTGraph, - AttributeMap defaultVertexAttributes, - AttributeMap defaultEdgeAttributes) - { - this( - jGraphTGraph, - defaultVertexAttributes, - defaultEdgeAttributes, - new DefaultCellFactory()); - } - - /** - * Constructs a new JGraph model adapter for the specified JGraphT graph. - * - * @param jGraphTGraph the JGraphT graph for which JGraph model adapter to - * be created. null is NOT permitted. - * @param defaultVertexAttributes a default map of JGraph attributes to - * format vertices. null is NOT permitted. - * @param defaultEdgeAttributes a default map of JGraph attributes to format - * edges. null is NOT permitted. - * @param cellFactory a {@link CellFactory} to be used to create the JGraph - * cells. null is NOT permitted. - * - * @throws IllegalArgumentException - */ - public JGraphModelAdapter( - Graph jGraphTGraph, - AttributeMap defaultVertexAttributes, - AttributeMap defaultEdgeAttributes, - CellFactory cellFactory) - { - super(); - - if ((jGraphTGraph == null) - || (defaultVertexAttributes == null) - || (defaultEdgeAttributes == null) - || (cellFactory == null)) - { - throw new IllegalArgumentException("null is NOT permitted"); - } - - jtGraph = new ShieldedGraph(jGraphTGraph); - setDefaultVertexAttributes(defaultVertexAttributes); - setDefaultEdgeAttributes(defaultEdgeAttributes); - this.cellFactory = cellFactory; - - if (jGraphTGraph instanceof ListenableGraph) { - ListenableGraph g = (ListenableGraph) jGraphTGraph; - g.addGraphListener(new JGraphTListener()); - } - - for ( - Iterator i = jGraphTGraph.vertexSet().iterator(); - i.hasNext();) - { - handleJGraphTAddedVertex(i.next()); - } - - for (Iterator i = jGraphTGraph.edgeSet().iterator(); i.hasNext();) { - handleJGraphTAddedEdge(i.next()); - } - - this.addGraphModelListener(new JGraphListener()); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Creates and returns a map of attributes to be used as defaults for edge - * attributes, depending on the specified graph. - * - * @param jGraphTGraph the graph for which default edge attributes to be - * created. - * - * @return a map of attributes to be used as default for edge attributes. - */ - public static AttributeMap createDefaultEdgeAttributes( - Graph jGraphTGraph) - { - AttributeMap map = new AttributeMap(); - - if (jGraphTGraph instanceof DirectedGraph) { - GraphConstants.setLineEnd(map, GraphConstants.ARROW_TECHNICAL); - GraphConstants.setEndFill(map, true); - GraphConstants.setEndSize(map, 10); - } - - GraphConstants.setForeground(map, Color.decode("#25507C")); - GraphConstants.setFont( - map, - GraphConstants.DEFAULTFONT.deriveFont(Font.BOLD, 12)); - GraphConstants.setLineColor(map, Color.decode("#7AA1E6")); - - return map; - } - - /** - * Creates and returns a map of attributes to be used as defaults for vertex - * attributes. - * - * @return a map of attributes to be used as defaults for vertex attributes. - */ - public static AttributeMap createDefaultVertexAttributes() - { - AttributeMap map = new AttributeMap(); - Color c = Color.decode("#FF9900"); - - GraphConstants.setBounds(map, new Rectangle2D.Double(50, 50, 90, 30)); - GraphConstants.setBorder(map, BorderFactory.createRaisedBevelBorder()); - GraphConstants.setBackground(map, c); - GraphConstants.setForeground(map, Color.white); - GraphConstants.setFont( - map, - GraphConstants.DEFAULTFONT.deriveFont(Font.BOLD, 12)); - GraphConstants.setOpaque(map, true); - - return map; - } - - /** - * Returns the cell factory used to create the JGraph cells. - * - * @return the cell factory used to create the JGraph cells. - */ - public CellFactory getCellFactory() - { - return cellFactory; - } - - /** - * Sets the default edge attributes used for creating new JGraph edges. - * - * @param defaultEdgeAttributes the default edge attributes to set. - */ - public void setDefaultEdgeAttributes(AttributeMap defaultEdgeAttributes) - { - this.defaultEdgeAttributes = defaultEdgeAttributes; - } - - /** - * Returns the default edge attributes used for creating new JGraph edges. - * - * @return the default edge attributes used for creating new JGraph edges. - */ - public AttributeMap getDefaultEdgeAttributes() - { - return defaultEdgeAttributes; - } - - /** - * Sets the default vertex attributes used for creating new JGraph vertices. - * - * @param defaultVertexAttributes the default vertex attributes to set. - */ - public void setDefaultVertexAttributes( - AttributeMap defaultVertexAttributes) - { - this.defaultVertexAttributes = defaultVertexAttributes; - } - - /** - * Returns the default vertex attributes used for creating new JGraph - * vertices. - * - * @return the default vertex attributes used for creating new JGraph - * vertices. - */ - public AttributeMap getDefaultVertexAttributes() - { - return defaultVertexAttributes; - } - - /** - * Returns the JGraph edge cell that corresponds to the specified JGraphT - * edge. If no corresponding cell found, returns null. - * - * @param jGraphTEdge a JGraphT edge of the JGraphT graph. - * - * @return the JGraph edge cell that corresponds to the specified JGraphT - * edge, or null if no corresponding cell found. - */ - public DefaultEdge getEdgeCell(E jGraphTEdge) - { - return (DefaultEdge) edgeToCell.get(jGraphTEdge); - } - - /** - * Returns the JGraph vertex cell that corresponds to the specified JGraphT - * vertex. If no corresponding cell found, returns null. - * - * @param jGraphTVertex a JGraphT vertex of the JGraphT graph. - * - * @return the JGraph vertex cell that corresponds to the specified JGraphT - * vertex, or null if no corresponding cell found. - */ - public DefaultGraphCell getVertexCell(Object jGraphTVertex) - { - return (DefaultGraphCell) vertexToCell.get(jGraphTVertex); - } - - /** - * Returns the JGraph port cell that corresponds to the specified JGraphT - * vertex. If no corresponding port found, returns null. - * - * @param jGraphTVertex a JGraphT vertex of the JGraphT graph. - * - * @return the JGraph port cell that corresponds to the specified JGraphT - * vertex, or null if no corresponding cell found. - */ - public DefaultPort getVertexPort(Object jGraphTVertex) - { - DefaultGraphCell vertexCell = getVertexCell(jGraphTVertex); - - if (vertexCell == null) { - return null; - } else { - return (DefaultPort) vertexCell.getChildAt(0); - } - } - - /** - * Adds/removes an edge to/from the underlying JGraphT graph according to - * the change in the specified JGraph edge. If both vertices are connected, - * we ensure to have a corresponding JGraphT edge. Otherwise, we ensure NOT - * to have a corresponding JGraphT edge. - * - *

    This method is to be called only for edges that have already been - * changed in the JGraph graph.

    - * - * @param jEdge the JGraph edge that has changed. - */ - void handleJGraphChangedEdge(org.jgraph.graph.Edge jEdge) - { - if (isDangling(jEdge)) { - if (cellToEdge.containsKey(jEdge)) { - // a non-dangling edge became dangling -- remove the JGraphT - // edge by faking as if the edge is removed from the JGraph. - // TODO: Consider keeping the JGraphT edges outside the graph - // to avoid loosing user data, such as weights. - handleJGraphRemovedEdge(jEdge); - } else { - // a dangling edge is still dangling -- just ignore. - } - } else { - // edge is not dangling - if (cellToEdge.containsKey(jEdge)) { - // edge already has a corresponding JGraphT edge. - // check if any change to its endpoints. - E jtEdge = cellToEdge.get(jEdge); - - Object jSource = getSourceVertex(this, jEdge); - Object jTarget = getTargetVertex(this, jEdge); - - Object jtSource = cellToVertex.get(jSource); - Object jtTarget = cellToVertex.get(jTarget); - - if ((jtGraph.getEdgeSource(jtEdge) == jtSource) - && (jtGraph.getEdgeTarget(jtEdge) == jtTarget)) - { - // no change in edge's endpoints -- nothing to do. - } else { - // edge's end-points have changed -- need to refresh the - // JGraphT edge. Refresh by faking as if the edge has been - // removed from JGraph and then added again. - // ALSO HERE: consider an alternative that maintains user - // data - handleJGraphRemovedEdge(jEdge); - handleJGraphInsertedEdge(jEdge); - } - } else { - // a new edge - handleJGraphInsertedEdge(jEdge); - } - } - } - - /** - * Adds to the underlying JGraphT graph an edge that corresponds to the - * specified JGraph edge. If the specified JGraph edge is a dangling edge, - * it is NOT added to the underlying JGraphT graph. - * - *

    This method is to be called only for edges that have already been - * added to the JGraph graph.

    - * - * @param jEdge the JGraph edge that has been added. - */ - void handleJGraphInsertedEdge(org.jgraph.graph.Edge jEdge) - { - if (isDangling(jEdge)) { - // JGraphT forbid dangling edges so we cannot add the edge yet. If - // later the edge becomes connected, we will add it. - } else { - // FIXME hb 28-nov-05: waiting for jgraph to go generic - Object jSource = getSourceVertex(this, jEdge); - Object jTarget = getTargetVertex(this, jEdge); - - V jtSource = cellToVertex.get(jSource); - V jtTarget = cellToVertex.get(jTarget); - - E jtEdge = jtGraph.addEdge(jtSource, jtTarget); - - if (jtEdge != null) { - cellToEdge.put(jEdge, jtEdge); - edgeToCell.put(jtEdge, jEdge); - } else { - // Adding failed because user is using a JGraphT graph the - // forbids parallel edges. - // For consistency, we remove the edge from the JGraph too. - internalRemoveCell(jEdge); - System.err.println( - "Warning: an edge was deleted because the underlying " - + "JGraphT graph refused to create it. " - + "This situation can happen when a constraint of the " - + "underlying graph is violated, e.g., an attempt to add " - + "a parallel edge or a self-loop to a graph that forbids " - + "them. To avoid this message, make sure to use a " - + "suitable underlying JGraphT graph."); - } - } - } - - /** - * Adds to the underlying JGraphT graph a vertex corresponding to the - * specified JGraph vertex. In JGraph, two vertices with the same user - * object are in principle allowed; in JGraphT, this would lead to duplicate - * vertices, which is not allowed. So if such vertex already exists, the - * specified vertex is REMOVED from the JGraph graph and a a warning is - * printed. - * - *

    This method is to be called only for vertices that have already been - * added to the JGraph graph.

    - * - * @param jVertex the JGraph vertex that has been added. - */ - @SuppressWarnings("unchecked") - void handleJGraphInsertedVertex(GraphCell jVertex) - { - V jtVertex; - - if (jVertex instanceof DefaultGraphCell) { - // FIXME hb 28-nov-05: waiting for jgraph to go generic - jtVertex = (V) ((DefaultGraphCell) jVertex).getUserObject(); - } else { - // FIXME: Why toString? Explain if for a good reason otherwise fix. - jtVertex = (V) jVertex.toString(); - } - - if (vertexToCell.containsKey(jtVertex)) { - // We have to remove the new vertex, because it would lead to - // duplicate vertices. We can't use ShieldedGraph.removeVertex for - // that, because it would remove the wrong (existing) vertex. - System.err.println( - "Warning: detected two JGraph vertices with " - + "the same JGraphT vertex as user object. It is an " - + "indication for a faulty situation that should NOT happen." - + "Removing vertex: " + jVertex); - internalRemoveCell(jVertex); - } else { - jtGraph.addVertex(jtVertex); - - cellToVertex.put(jVertex, jtVertex); - vertexToCell.put(jtVertex, jVertex); - } - } - - /** - * Removes the edge corresponding to the specified JGraph edge from the - * JGraphT graph. If the specified edge is not contained in {@link - * #cellToEdge}, it is silently ignored. - * - *

    This method is to be called only for edges that have already been - * removed from the JGraph graph.

    - * - * @param jEdge the JGraph edge that has been removed. - */ - void handleJGraphRemovedEdge(org.jgraph.graph.Edge jEdge) - { - if (cellToEdge.containsKey(jEdge)) { - E jtEdge = cellToEdge.get(jEdge); - - jtGraph.removeEdge(jtEdge); - - cellToEdge.remove(jEdge); - edgeToCell.remove(jtEdge); - } - } - - /** - * Removes the vertex corresponding to the specified JGraph vertex from the - * JGraphT graph. If the specified vertex is not contained in {@link - * #cellToVertex}, it is silently ignored. - * - *

    If any edges are incident with this vertex, we first remove them from - * the both graphs, because otherwise the JGraph graph would leave them - * intact and the JGraphT graph would throw them out. TODO: Revise this - * behavior now that we gracefully tolerate dangling edges. It might be - * possible to remove just the JGraphT edges. The JGraph edges will be left - * dangling, as a result.

    - * - *

    This method is to be called only for vertices that have already been - * removed from the JGraph graph.

    - * - * @param jVertex the JGraph vertex that has been removed. - */ - void handleJGraphRemovedVertex(GraphCell jVertex) - { - if (cellToVertex.containsKey(jVertex)) { - V jtVertex = cellToVertex.get(jVertex); - Set jtIncidentEdges = jtGraph.edgesOf(jtVertex); - - if (!jtIncidentEdges.isEmpty()) { - // We can't just call removeAllEdges with this list: that - // would throw a ConcurrentModificationException. So we create - // a shallow copy. - // This also triggers removal of the corresponding JGraph - // edges. - jtGraph.removeAllEdges(new ArrayList(jtIncidentEdges)); - } - - jtGraph.removeVertex(jtVertex); - - cellToVertex.remove(jVertex); - vertexToCell.remove(jtVertex); - } - } - - /** - * Adds the specified JGraphT edge to be reflected by this graph model. To - * be called only for edges that already exist in the JGraphT graph. - * - * @param jtEdge a JGraphT edge to be reflected by this graph model. - */ - void handleJGraphTAddedEdge(E jtEdge) - { - DefaultEdge edgeCell = cellFactory.createEdgeCell(jtEdge); - edgeToCell.put(jtEdge, edgeCell); - cellToEdge.put(edgeCell, jtEdge); - - ConnectionSet cs = new ConnectionSet(); - cs.connect( - edgeCell, - getVertexPort(jtGraph.getEdgeSource(jtEdge)), - getVertexPort(jtGraph.getEdgeTarget(jtEdge))); - - internalInsertCell(edgeCell, createEdgeAttributeMap(edgeCell), cs); - } - - /** - * Adds the specified JGraphT vertex to be reflected by this graph model. To - * be called only for edges that already exist in the JGraphT graph. - * - * @param jtVertex a JGraphT vertex to be reflected by this graph model. - */ - void handleJGraphTAddedVertex(V jtVertex) - { - DefaultGraphCell vertexCell = cellFactory.createVertexCell(jtVertex); - vertexCell.add(new DefaultPort()); - - vertexToCell.put(jtVertex, vertexCell); - cellToVertex.put(vertexCell, jtVertex); - - internalInsertCell( - vertexCell, - createVertexAttributeMap(vertexCell), - null); - } - - /** - * Removes the specified JGraphT vertex from being reflected by this graph - * model. To be called only for vertices that have already been removed from - * the JGraphT graph. - * - * @param jtVertex a JGraphT vertex to be removed from being reflected by - * this graph model. - */ - void handleJGraphTRemoveVertex(Object jtVertex) - { - DefaultGraphCell vertexCell = - (DefaultGraphCell) vertexToCell.remove(jtVertex); - cellToVertex.remove(vertexCell); - - List ports = new ArrayList(); - - for (Object child : vertexCell.getChildren()) { - if (this.isPort(child)) { - ports.add(child); - } - } - this.remove(ports.toArray()); - - internalRemoveCell(vertexCell); - } - - /** - * Removes the specified JGraphT edge from being reflected by this graph - * model. To be called only for edges that have already been removed from - * the JGraphT graph. - * - * @param jtEdge a JGraphT edge to be removed from being reflected by this - * graph model. - */ - void handleJGraphTRemovedEdge(E jtEdge) - { - DefaultEdge edgeCell = (DefaultEdge) edgeToCell.remove(jtEdge); - cellToEdge.remove(edgeCell); - internalRemoveCell(edgeCell); - } - - /** - * Tests if the specified JGraph edge is 'dangling', that is having at least - * one endpoint which is not connected to a vertex. - * - * @param jEdge the JGraph edge to be tested for being dangling. - * - * @return true if the specified edge is dangling, otherwise - * false. - */ - private boolean isDangling(org.jgraph.graph.Edge jEdge) - { - Object jSource = getSourceVertex(this, jEdge); - Object jTarget = getTargetVertex(this, jEdge); - - return !cellToVertex.containsKey(jSource) - || !cellToVertex.containsKey(jTarget); - } - - @SuppressWarnings("unchecked") - private AttributeMap createEdgeAttributeMap(DefaultEdge edgeCell) - { - AttributeMap attrs = new AttributeMap(); - - // FIXME hb 28-nov-05: waiting for graph to go generic - attrs.put(edgeCell, getDefaultEdgeAttributes().clone()); - - return attrs; - } - - @SuppressWarnings("unchecked") - private AttributeMap createVertexAttributeMap(GraphCell vertexCell) - { - AttributeMap attrs = new AttributeMap(); - - // FIXME hb 28-nov-05: waiting for graph to go generic - attrs.put(vertexCell, getDefaultVertexAttributes().clone()); - - return attrs; - } - - /** - * Inserts the specified cell into the JGraph graph model. - * - * @param cell - * @param attrs - * @param cs - */ - // FIXME hb 28-nov-05: waiting for graph to go generic - private void internalInsertCell( - GraphCell cell, - AttributeMap attrs, - ConnectionSet cs) - { - jCellsBeingAdded.add(cell); - insert(new Object[] { cell }, attrs, cs, null, null); - jCellsBeingAdded.remove(cell); - } - - /** - * Removed the specified cell from the JGraph graph model. - * - * @param cell - */ - private void internalRemoveCell(GraphCell cell) - { - jCellsBeingRemoved.add(cell); - remove(new Object[] { cell }); - jCellsBeingRemoved.remove(cell); - } - - //~ Inner Interfaces ------------------------------------------------------- - - /** - * Creates the JGraph cells that reflect the respective JGraphT elements. - * - * @author Barak Naveh - * @since Dec 12, 2003 - */ - public static interface CellFactory - { - /** - * Creates an edge cell that contains its respective JGraphT edge. - * - * @param jGraphTEdge a JGraphT edge to be contained. - * - * @return an edge cell that contains its respective JGraphT edge. - */ - public DefaultEdge createEdgeCell(EE jGraphTEdge); - - /** - * Creates a vertex cell that contains its respective JGraphT vertex. - * - * @param jGraphTVertex a JGraphT vertex to be contained. - * - * @return a vertex cell that contains its respective JGraphT vertex. - */ - public DefaultGraphCell createVertexCell(VV jGraphTVertex); - } - - //~ Inner Classes ---------------------------------------------------------- - - /** - * A simple default cell factory. - * - * @author Barak Naveh - * @since Dec 12, 2003 - */ - public static class DefaultCellFactory - implements CellFactory, - Serializable - { - private static final long serialVersionUID = 3690194343461861173L; - - /** - * @see JGraphModelAdapter.CellFactory#createEdgeCell(Object) - */ - public DefaultEdge createEdgeCell(EE jGraphTEdge) - { - return new DefaultEdge(jGraphTEdge); - } - - /** - * @see JGraphModelAdapter.CellFactory#createVertexCell(Object) - */ - public DefaultGraphCell createVertexCell(VV jGraphTVertex) - { - return new DefaultGraphCell(jGraphTVertex); - } - } - - /** - *

    Inner class listening to the GraphModel. If something is changed in - * the GraphModel, this Listener gets notified and propagates the change - * back to the JGraphT graph, if it didn't originate there.

    - * - *

    If this change contains changes that would make this an illegal - * JGraphT graph, like adding an edge that is incident with only one vertex, - * the illegal parts of the change are undone.

    - */ - private class JGraphListener - implements GraphModelListener, - Serializable - { - private static final long serialVersionUID = 3544673988098865209L; - - /** - * This method is called for all JGraph changes. - * - * @param e - */ - public void graphChanged(GraphModelEvent e) - { - // We first remove edges that have to be removed, then we - // remove vertices, then we add vertices and finally we add - // edges. Otherwise, things might go wrong: for example, if we - // would first remove vertices and then edges, removal of the - // vertices might induce 'automatic' removal of edges. If we - // later attempt to re-remove these edges, we get confused. - GraphModelChange change = e.getChange(); - - Object [] removedCells = change.getRemoved(); - - if (removedCells != null) { - handleRemovedEdges(filterEdges(removedCells)); - handleRemovedVertices(filterVertices(removedCells)); - } - - Object [] insertedCells = change.getInserted(); - - if (insertedCells != null) { - handleInsertedVertices(filterVertices(insertedCells)); - handleInsertedEdges(filterEdges(insertedCells)); - } - - // Now handle edges that became 'dangling' or became connected. - Object [] changedCells = change.getChanged(); - - if (changedCells != null) { - handleChangedEdges(filterEdges(changedCells)); - } - } - - /** - * Filters a list of edges out of an array of JGraph GraphCell objects. - * Other objects are thrown away. - * - * @param cells Array of cells to be filtered. - * - * @return a list of edges. - */ - private List filterEdges(Object [] cells) - { - List jEdges = new ArrayList(); - - for (int i = 0; i < cells.length; i++) { - if (cells[i] instanceof org.jgraph.graph.Edge) { - jEdges.add(cells[i]); - } - } - - return jEdges; - } - - /** - * Filters a list of vertices out of an array of JGraph GraphCell - * objects. Other objects are thrown away. - * - * @param cells Array of cells to be filtered. - * - * @return a list of vertices. - */ - private List filterVertices(Object [] cells) - { - List jVertices = new ArrayList(); - - for (int i = 0; i < cells.length; i++) { - Object cell = cells[i]; - - if (cell instanceof org.jgraph.graph.Edge) { - // ignore -- we don't care about edges. - } else if (cell instanceof Port) { - // ignore -- we don't care about ports. - } else if (cell instanceof DefaultGraphCell) { - DefaultGraphCell graphCell = (DefaultGraphCell) cell; - - // If a DefaultGraphCell has a Port as a child, it is a - // vertex. - // Note: do not change the order of following conditions; - // the code uses the short-circuit evaluation of ||. - if (graphCell.isLeaf() - || (graphCell.getFirstChild() instanceof Port)) - { - jVertices.add(cell); - } - } else if (cell instanceof GraphCell) { - // If it is not a DefaultGraphCell, it doesn't have - // children. - jVertices.add(cell); - } - } - - return jVertices; - } - - private void handleChangedEdges(List jEdges) - { - for (Iterator i = jEdges.iterator(); i.hasNext();) { - org.jgraph.graph.Edge jEdge = (org.jgraph.graph.Edge) i.next(); - - handleJGraphChangedEdge(jEdge); - } - } - - private void handleInsertedEdges(List jEdges) - { - for (Iterator i = jEdges.iterator(); i.hasNext();) { - org.jgraph.graph.Edge jEdge = (org.jgraph.graph.Edge) i.next(); - - if (!jCellsBeingAdded.remove(jEdge)) { - handleJGraphInsertedEdge(jEdge); - } - } - } - - private void handleInsertedVertices(List jVertices) - { - for (Iterator i = jVertices.iterator(); i.hasNext();) { - GraphCell jVertex = (GraphCell) i.next(); - - if (!jCellsBeingAdded.remove(jVertex)) { - handleJGraphInsertedVertex(jVertex); - } - } - } - - private void handleRemovedEdges(List jEdges) - { - for (Iterator i = jEdges.iterator(); i.hasNext();) { - org.jgraph.graph.Edge jEdge = (org.jgraph.graph.Edge) i.next(); - - if (!jCellsBeingRemoved.remove(jEdge)) { - handleJGraphRemovedEdge(jEdge); - } - } - } - - private void handleRemovedVertices(List jVertices) - { - for (Iterator i = jVertices.iterator(); i.hasNext();) { - GraphCell jVertex = (GraphCell) i.next(); - - if (!jCellsBeingRemoved.remove(jVertex)) { - handleJGraphRemovedVertex(jVertex); - } - } - } - } - - /** - * A listener on the underlying JGraphT graph. This listener is used to keep - * the JGraph model in sync. Whenever one of the event handlers is called, - * it first checks whether the change is due to a previous change in the - * JGraph model. If it is, then no action is taken. - * - * @author Barak Naveh - * @since Aug 2, 2003 - */ - private class JGraphTListener - implements GraphListener, - Serializable - { - private static final long serialVersionUID = 3616724963609360440L; - - /** - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public void edgeAdded(GraphEdgeChangeEvent e) - { - E jtEdge = e.getEdge(); - - if (!jtElementsBeingAdded.remove(jtEdge)) { - handleJGraphTAddedEdge(jtEdge); - } - } - - /** - * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) - */ - public void edgeRemoved(GraphEdgeChangeEvent e) - { - E jtEdge = e.getEdge(); - - if (!jtElementsBeingRemoved.remove(jtEdge)) { - handleJGraphTRemovedEdge(jtEdge); - } - } - - /** - * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) - */ - public void vertexAdded(GraphVertexChangeEvent e) - { - V jtVertex = e.getVertex(); - - if (!jtElementsBeingAdded.remove(jtVertex)) { - handleJGraphTAddedVertex(jtVertex); - } - } - - /** - * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) - */ - public void vertexRemoved(GraphVertexChangeEvent e) - { - V jtVertex = e.getVertex(); - - if (!jtElementsBeingRemoved.remove(jtVertex)) { - handleJGraphTRemoveVertex(jtVertex); - } - } - } - - /** - * A wrapper around a JGraphT graph that ensures a few atomic operations. - */ - private class ShieldedGraph - { - private final Graph graph; - - ShieldedGraph(Graph graph) - { - this.graph = graph; - } - - E addEdge(V jtSource, V jtTarget) - { - E jtEdge = graph.getEdgeFactory().createEdge(jtSource, jtTarget); - jtElementsBeingAdded.add(jtEdge); - - boolean added = graph.addEdge(jtSource, jtTarget, jtEdge); - jtElementsBeingAdded.remove(jtEdge); - - return added ? jtEdge : null; - } - - V getEdgeSource(E e) - { - return graph.getEdgeSource(e); - } - - V getEdgeTarget(E e) - { - return graph.getEdgeTarget(e); - } - - void addVertex(V jtVertex) - { - jtElementsBeingAdded.add(jtVertex); - graph.addVertex(jtVertex); - jtElementsBeingAdded.remove(jtVertex); - } - - Set edgesOf(V vertex) - { - return graph.edgesOf(vertex); - } - - boolean removeAllEdges(Collection edges) - { - return graph.removeAllEdges(edges); - } - - void removeEdge(E jtEdge) - { - jtElementsBeingRemoved.add(jtEdge); - graph.removeEdge(jtEdge); - jtElementsBeingRemoved.remove(jtEdge); - } - - void removeVertex(V jtVertex) - { - jtElementsBeingRemoved.add(jtVertex); - graph.removeVertex(jtVertex); - jtElementsBeingRemoved.remove(jtVertex); - } - } -} - -// End JGraphModelAdapter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphXAdapter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphXAdapter.java new file mode 100644 index 00000000000..42cd56f3ccd --- /dev/null +++ b/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphXAdapter.java @@ -0,0 +1,306 @@ +/* + * (C) Copyright 2013-2023, 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. + * + * @param graph casted to graph + * + * @throws IllegalArgumentException if the graph is null. + */ + 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. + * + * @param graph is a graph + * + * @throws IllegalArgumentException if the parameter is null + */ + 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); + } + } +} diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/MatrixExporter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/MatrixExporter.java deleted file mode 100644 index 17c3c129e6b..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/MatrixExporter.java +++ /dev/null @@ -1,264 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * MatrixExporter.java - * ------------------ - * (C) Copyright 2005-2008, by Charles Fry and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 13-Dec-2005 : Initial Version (CF); - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; -import org.jgrapht.util.*; - - -/** - * Exports a graph to a plain text matrix format, which can be processed by - * matrix manipulation software, such as - * MTJ or MATLAB. - * - * @author Charles Fry - */ -public class MatrixExporter -{ - //~ Instance fields -------------------------------------------------------- - - private String delimiter = " "; - private String prefix = ""; - private String suffix = ""; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new MatrixExporter object. - */ - public MatrixExporter() - { - } - - //~ Methods ---------------------------------------------------------------- - - private void println( - PrintWriter out, - String fromName, - String toName, - String value) - { - out.println( - prefix + fromName + suffix + delimiter - + prefix + toName + suffix + delimiter - + prefix + value + suffix); - } - - /** - * 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 symetric. - * - * @param output the writer to which the graph to be exported. - * @param g the graph to be exported. - */ - public void exportAdjacencyMatrix(Writer output, UndirectedGraph g) - { - PrintWriter out = new PrintWriter(output); - - VertexNameProvider nameProvider = new IntegerNameProvider(); - for (V from : g.vertexSet()) { - // assign ids in vertex set iteration order - nameProvider.getVertexName(from); - } - - for (V from : g.vertexSet()) { - exportAdjacencyMatrixVertex( - out, - nameProvider, - from, - Graphs.neighborListOf(g, from)); - } - - out.flush(); - } - - /** - * 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 directed edges - * going from one vertex to another. - * - * @param output the writer to which the graph to be exported. - * @param g the graph to be exported. - */ - public void exportAdjacencyMatrix(Writer output, DirectedGraph g) - { - PrintWriter out = new PrintWriter(output); - - VertexNameProvider nameProvider = new IntegerNameProvider(); - for (V from : g.vertexSet()) { - // assign ids in vertex set iteration order - nameProvider.getVertexName(from); - } - - for (V from : g.vertexSet()) { - exportAdjacencyMatrixVertex( - out, - nameProvider, - from, - Graphs.successorListOf(g, from)); - } - - out.flush(); - } - - private void exportAdjacencyMatrixVertex( - PrintWriter out, - VertexNameProvider nameProvider, - V from, - List neighbors) - { - String fromName = nameProvider.getVertexName(from); - Map counts = - new LinkedHashMap(); - for (V to : neighbors) { - String toName = nameProvider.getVertexName(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(); - println(out, fromName, toName, count.toString()); - } - } - - /** - * Exports the specified graph into a plain text file format containing a - * sparse representation of the graph's Laplacian matrix. Laplacian matrices - * are only defined for simple graphs, so edge direction, multiple edges, - * loops, and weights are all ignored when creating the Laplacian matrix. If - * you're unsure about Laplacian matrices, see: - * http://mathworld.wolfram.com/LaplacianMatrix.html. - * - * @param output the writer to which the graph is to be exported. - * @param g the graph to be exported. - */ - public void exportLaplacianMatrix(Writer output, UndirectedGraph g) - { - PrintWriter out = new PrintWriter(output); - - VertexNameProvider nameProvider = new IntegerNameProvider(); - for (V from : g.vertexSet()) { - // assign ids in vertex set iteration order - nameProvider.getVertexName(from); - } - - for (V from : g.vertexSet()) { - String fromName = nameProvider.getVertexName(from); - - // TODO modify Graphs to return neighbor sets - List neighbors = Graphs.neighborListOf(g, from); - println( - out, - fromName, - fromName, - Integer.toString(neighbors.size())); - for (V to : neighbors) { - String toName = nameProvider.getVertexName(to); - println(out, fromName, toName, "-1"); - } - } - - out.flush(); - } - - /** - * Exports the specified graph into a plain text file format containing a - * sparse representation of the graph's normalized Laplacian matrix. - * Laplacian matrices are only defined for simple graphs, so edge direction, - * multiple edges, loops, and weights are all ignored when creating the - * Laplacian matrix. If you're unsure about normalized Laplacian matrices, - * see: - * http://mathworld.wolfram.com/LaplacianMatrix.html. - * - * @param output the writer to which the graph is to be exported. - * @param g the graph to be exported. - */ - public void exportNormalizedLaplacianMatrix( - Writer output, - UndirectedGraph g) - { - PrintWriter out = new PrintWriter(output); - - VertexNameProvider nameProvider = new IntegerNameProvider(); - for (V from : g.vertexSet()) { - // assign ids in vertex set iteration order - nameProvider.getVertexName(from); - } - - for (V from : g.vertexSet()) { - String fromName = nameProvider.getVertexName(from); - Set neighbors = - new LinkedHashSet(Graphs.neighborListOf(g, from)); - if (neighbors.isEmpty()) { - println(out, fromName, fromName, "0"); - } else { - println(out, fromName, fromName, "1"); - - for (V to : neighbors) { - String toName = nameProvider.getVertexName(to); - double value = - -1 / Math.sqrt(g.degreeOf(from) * g.degreeOf(to)); - println(out, fromName, toName, Double.toString(value)); - } - } - } - - out.flush(); - } -} - -// End MatrixExporter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/StringEdgeNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/StringEdgeNameProvider.java deleted file mode 100644 index 61a1fb455e6..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/StringEdgeNameProvider.java +++ /dev/null @@ -1,64 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * StringNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Trevor Harmon. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -/** - * Generates edge names by invoking {@link #toString()} on them. This assumes - * that the edge's {@link #toString()} method returns a unique String - * representation for each edge. - * - * @author Trevor Harmon - */ -public class StringEdgeNameProvider - implements EdgeNameProvider -{ - //~ Constructors ----------------------------------------------------------- - - public StringEdgeNameProvider() - { - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the String representation an edge. - * - * @param edge the edge to be named - */ - public String getEdgeName(E edge) - { - return edge.toString(); - } -} - -// End StringEdgeNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/StringNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/StringNameProvider.java deleted file mode 100644 index 47e90a1dd62..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/StringNameProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * StringNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Charles Fry and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 13-Dec-2005 : Initial Version (CF); - * - */ -package org.jgrapht.ext; - -import org.jgrapht.event.*; - - -/** - * Generates vertex names by invoking {@link #toString()} on them. This assumes - * that the vertex's {@link #toString()} method returns a unique String - * representation for each vertex. - * - * @author Charles Fry - */ -public class StringNameProvider - implements VertexNameProvider -{ - //~ Constructors ----------------------------------------------------------- - - public StringNameProvider() - { - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Returns the String representation of the unique integer representing a - * vertex. - * - * @param vertex the vertex to be named - * - * @return the name of - * - * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) - */ - public String getVertexName(V vertex) - { - return vertex.toString(); - } -} - -// End StringNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/VertexNameProvider.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/VertexNameProvider.java deleted file mode 100644 index 1cc86e97c5e..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/VertexNameProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * VertexNameProvider.java - * ------------------ - * (C) Copyright 2005-2008, by Avner Linder and Contributors. - * - * Original Author: Avner Linder - * - * $Id$ - * - * Changes - * ------- - * 27-May-2004 : Initial Version (AL); - * 13-Dec-2005 : Split out of VisioExporter (CF); - * - */ -package org.jgrapht.ext; - -/** - * Assigns a display name for each of the graph vertices. - */ -public interface VertexNameProvider -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Returns a unique name for a vertex. This is useful when exporting a a - * graph, as it ensures that all vertices are assigned simple, consistent - * names. - * - * @param vertex the vertex to be named - * - * @return the name of the vertex - */ - public String getVertexName(V vertex); -} - -// End VertexNameProvider.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/VisioExporter.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/VisioExporter.java deleted file mode 100644 index b4a9c30f7ec..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/VisioExporter.java +++ /dev/null @@ -1,149 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------ - * VisioExporter.java - * ------------------ - * (C) Copyright 2003-2008, by Avner Linder and Contributors. - * - * Original Author: Avner Linder - * Contributor(s): Barak Naveh - * - * $Id$ - * - * Changes - * ------- - * 27-May-2004 : Initial Version (AL); - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * 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. - *
    - *

    - * - * @author Avner Linder - */ -public class VisioExporter -{ - //~ Instance fields -------------------------------------------------------- - - private VertexNameProvider vertexNameProvider; - - //~ Constructors ----------------------------------------------------------- - - /** - * Creates a new VisioExporter object with the specified naming policy. - * - * @param vertexNameProvider the vertex name provider to be used for naming - * the Visio shapes. - */ - public VisioExporter(VertexNameProvider vertexNameProvider) - { - this.vertexNameProvider = vertexNameProvider; - } - - /** - * Creates a new VisioExporter object. - */ - public VisioExporter() - { - this(new StringNameProvider()); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * Exports the specified graph into a Visio csv file format. - * - * @param output the print stream to which the graph to be exported. - * @param g the graph to be exported. - */ - public void export(OutputStream output, Graph g) - { - PrintStream out = new PrintStream(output); - - for (Iterator i = g.vertexSet().iterator(); i.hasNext();) { - exportVertex(out, i.next()); - } - - for (Iterator i = g.edgeSet().iterator(); i.hasNext();) { - exportEdge(out, i.next(), g); - } - - out.flush(); - } - - private void exportEdge(PrintStream out, E edge, Graph g) - { - String sourceName = - vertexNameProvider.getVertexName(g.getEdgeSource(edge)); - String targetName = - vertexNameProvider.getVertexName(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(PrintStream out, V vertex) - { - String name = vertexNameProvider.getVertexName(vertex); - - out.print("Shape,"); - out.print(name); - out.print(",,"); // MasterName field left empty - out.print(name); - out.print("\n"); - } -} - -// End VisioExporter.java diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/package-info.java b/jgrapht-ext/src/main/java/org/jgrapht/ext/package-info.java new file mode 100644 index 00000000000..bc3cc2d1676 --- /dev/null +++ b/jgrapht-ext/src/main/java/org/jgrapht/ext/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Extensions and integration means to other products. + */ +package org.jgrapht.ext; diff --git a/jgrapht-ext/src/main/java/org/jgrapht/ext/package.html b/jgrapht-ext/src/main/java/org/jgrapht/ext/package.html deleted file mode 100644 index f96cf728721..00000000000 --- a/jgrapht-ext/src/main/java/org/jgrapht/ext/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -

    -Extensions and integration means to other products. -

    - - \ No newline at end of file diff --git a/jgrapht-ext/src/test/java/org/jgrapht/ext/DOTExporterTest.java b/jgrapht-ext/src/test/java/org/jgrapht/ext/DOTExporterTest.java deleted file mode 100644 index 8baf8db9ecb..00000000000 --- a/jgrapht-ext/src/test/java/org/jgrapht/ext/DOTExporterTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * DOTExporterTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import java.util.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Trevor Harmon - */ -public class DOTExporterTest - extends TestCase -{ - //~ 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"); - - // TODO jvs 23-Dec-2006: externalized diff-based testing framework - - 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; - - //~ Methods ---------------------------------------------------------------- - - public void testUndirected() - { - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addEdge(V1, V2); - g.addVertex(V3); - g.addEdge(V3, V1); - - StringWriter w = new StringWriter(); - ComponentAttributeProvider vertexAttributeProvider = - new ComponentAttributeProvider() { - public Map getComponentAttributes(String v) - { - Map map = - new LinkedHashMap(); - if (v.equals(V1)) { - map.put("label", "a"); - } else if (v.equals(V2)) { - map.put("x", "y"); - } else { - map = null; - } - return map; - } - }; - - DOTExporter exporter = - new DOTExporter( - new IntegerNameProvider(), - null, - null, - vertexAttributeProvider, - null); - exporter.export(w, g); - assertEquals(UNDIRECTED, w.toString()); - } - - public void testValidNodeIDs() - { - DOTExporter exporter = - new DOTExporter( - new StringNameProvider(), - new StringNameProvider(), - null); - - 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.export(new StringWriter(), graph); - } - - 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.export(new StringWriter(), graph); - Assert.fail(vertex); - } catch (RuntimeException re) { - // this is a negative test so exception is expected - } - } - } -} - -// End DOTExporterTest.java diff --git a/jgrapht-ext/src/test/java/org/jgrapht/ext/GmlExporterTest.java b/jgrapht-ext/src/test/java/org/jgrapht/ext/GmlExporterTest.java deleted file mode 100644 index ee2f74f40cc..00000000000 --- a/jgrapht-ext/src/test/java/org/jgrapht/ext/GmlExporterTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * GmlExporterTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: John V. Sichi - * - * $Id$ - * - * Changes - * ------- - * 23-Dec-2006 : Initial revision (JVS); - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author John V. Sichi - */ -public class GmlExporterTest - extends TestCase -{ - //~ 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"); - - // TODO jvs 23-Dec-2006: externalized diff-based testing framework - - 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 GmlExporter exporter = - new GmlExporter(); - - //~ Methods ---------------------------------------------------------------- - - public void testUndirected() - { - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addEdge(V1, V2); - g.addVertex(V3); - g.addEdge(V3, V1); - - StringWriter w = new StringWriter(); - exporter.export(w, g); - assertEquals(UNDIRECTED, w.toString()); - } -} - -// End GmlExporterTest.java diff --git a/jgrapht-ext/src/test/java/org/jgrapht/ext/GraphMLExporterTest.java b/jgrapht-ext/src/test/java/org/jgrapht/ext/GraphMLExporterTest.java deleted file mode 100644 index be4e7b2937a..00000000000 --- a/jgrapht-ext/src/test/java/org/jgrapht/ext/GraphMLExporterTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * GraphMLExporterTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Trevor Harmon - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import junit.framework.*; - -import org.custommonkey.xmlunit.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Trevor Harmon - */ -public class GraphMLExporterTest - extends TestCase -{ - //~ 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"); - - // TODO jvs 23-Dec-2006: externalized diff-based testing framework - - private static final String UNDIRECTED = - "" + NL - + "" - + NL - + "" + NL - + "" + NL - + "" + NL - + "" + NL - + "" + NL - + "" + NL - + "" + NL - + "" + NL; - - private static final GraphMLExporter exporter = - new GraphMLExporter(); - - //~ Methods ---------------------------------------------------------------- - - public void testUndirected() - throws Exception - { - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addEdge(V1, V2); - g.addVertex(V3); - g.addEdge(V3, V1); - - StringWriter w = new StringWriter(); - exporter.export(w, g); - - if (System.getProperty("java.vm.version").startsWith("1.4")) { - // NOTE jvs 16-Mar-2007: XML prefix mapping comes out - // with missing info on 1.4, so skip the verification part - // of the test. - return; - } - - XMLAssert.assertXMLEqual(UNDIRECTED, w.toString()); - } -} - -// End GraphMLExporterTest.java diff --git a/jgrapht-ext/src/test/java/org/jgrapht/ext/JGraphXAdapterTest.java b/jgrapht-ext/src/test/java/org/jgrapht/ext/JGraphXAdapterTest.java new file mode 100644 index 00000000000..e388dc7943d --- /dev/null +++ b/jgrapht-ext/src/test/java/org/jgrapht/ext/JGraphXAdapterTest.java @@ -0,0 +1,407 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.util.*; + +import com.mxgraph.model.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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); + assertArrayEquals(expectedArray, realArray); + + realArray = graphX.getVertexToCellMap().keySet().toArray(); + Arrays.sort(realArray); + assertArrayEquals(expectedArray, realArray); + + int edgesCount = graphX.getCellToEdgeMap().values().size(); + assertEquals(expectedEdges, edgesCount); + + edgesCount = graphX.getEdgeToCellMap().keySet().size(); + 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); + assertArrayEquals(expectedArray, realArray); + + realArray = graphX.getVertexToCellMap().keySet().toArray(); + Arrays.sort(realArray); + assertArrayEquals(expectedArray, realArray); + + int edgesCount = graphX.getCellToEdgeMap().values().size(); + assertEquals(expectedEdges, edgesCount); + + edgesCount = graphX.getEdgeToCellMap().keySet().size(); + 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); + assertArrayEquals(expectedArray, realArray); + + realArray = graphX.getVertexToCellMap().keySet().toArray(); + Arrays.sort(realArray); + assertArrayEquals(expectedArray, realArray); + + edgesCount = graphX.getCellToEdgeMap().values().size(); + assertEquals(expectedEdgesAfterRemove, edgesCount); + + edgesCount = graphX.getEdgeToCellMap().keySet().size(); + 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 + 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); + assertArrayEquals(expectedArray, realArray); + + realArray = graphX.getVertexToCellMap().keySet().toArray(); + Arrays.sort(realArray); + assertArrayEquals(expectedArray, realArray); + + int edgesCount = graphX.getCellToEdgeMap().values().size(); + assertEquals(expectedEdges, edgesCount); + + edgesCount = graphX.getEdgeToCellMap().keySet().size(); + 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); + assertArrayEquals(expectedArray, realArray); + + realArray = graphX.getVertexToCellMap().keySet().toArray(); + Arrays.sort(realArray); + assertArrayEquals(expectedArray, realArray); + + int edgesCount = graphX.getCellToEdgeMap().values().size(); + assertEquals(expectedEdges, edgesCount); + + edgesCount = graphX.getEdgeToCellMap().keySet().size(); + 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); + } +} diff --git a/jgrapht-ext/src/test/java/org/jgrapht/ext/MatrixExporterTest.java b/jgrapht-ext/src/test/java/org/jgrapht/ext/MatrixExporterTest.java deleted file mode 100644 index 519dfa69e6b..00000000000 --- a/jgrapht-ext/src/test/java/org/jgrapht/ext/MatrixExporterTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------------------ - * MatrixExporterTest.java - * ------------------------------ - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * Original Author: Charles Fry - * - * $Id$ - * - * Changes - * ------- - * 12-Dec-2005 : Initial revision (CF); - * - */ -package org.jgrapht.ext; - -import java.io.*; - -import junit.framework.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * . - * - * @author Charles Fry - */ -public class MatrixExporterTest - extends TestCase -{ - //~ 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"); - - // TODO jvs 23-Dec-2006: externalized diff-based testing framework - - 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; - - private static final MatrixExporter exporter = - new MatrixExporter(); - - //~ Methods ---------------------------------------------------------------- - - public void testLaplacian() - { - UndirectedGraph g = - new SimpleGraph(DefaultEdge.class); - g.addVertex(V1); - g.addVertex(V2); - g.addEdge(V1, V2); - g.addVertex(V3); - g.addEdge(V3, V1); - - StringWriter w = new StringWriter(); - exporter.exportLaplacianMatrix(w, g); - assertEquals(LAPLACIAN, w.toString()); - - w = new StringWriter(); - exporter.exportNormalizedLaplacianMatrix(w, g); - assertEquals(NORMALIZED_LAPLACIAN, w.toString()); - } - - public void testAdjacencyUndirected() - { - UndirectedGraph 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); - - StringWriter w = new StringWriter(); - exporter.exportAdjacencyMatrix(w, g); - assertEquals(UNDIRECTED_ADJACENCY, w.toString()); - } - - public void testAdjacencyDirected() - { - DirectedGraph 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); - - Writer w = new StringWriter(); - exporter.exportAdjacencyMatrix(w, g); - assertEquals(DIRECTED_ADJACENCY, w.toString()); - } -} - -// End MatrixExporterTest.java diff --git a/jgrapht-guava/pom.xml b/jgrapht-guava/pom.xml new file mode 100644 index 00000000000..8aed6a96353 --- /dev/null +++ b/jgrapht-guava/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + 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 + + + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/jgrapht-guava/src/main/java/module-info.java b/jgrapht-guava/src/main/java/module-info.java new file mode 100644 index 00000000000..40999549cc0 --- /dev/null +++ b/jgrapht-guava/src/main/java/module-info.java @@ -0,0 +1,32 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides adaptors for the Google Guava graphs to be used with the + * JGraphT library. + * + * @since 1.5.0 + */ +module org.jgrapht.guava +{ + exports org.jgrapht.graph.guava; + + requires transitive org.jgrapht.core; + requires transitive com.google.common; + requires transitive org.jheaps; +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseGraphAdapter.java new file mode 100644 index 00000000000..60a89483dda --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseGraphAdapter.java @@ -0,0 +1,358 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 com.google.common.graph.Graph 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 + * + * @throws NullPointerException if {@code graph} is {@code null} + */ + 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 + * + * @throws NullPointerException if {@code graph} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if either one of {@code graph} or {@code vertexOrderMethod} is {@code null} + */ + 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 {@code v} must not be equal + * to any other vertex in the graph. More formally, the graph must not contain any vertex + * {@code v2} such that {@code 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 {@code 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 {@code e2} such that {@code 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)); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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 + * + * @throws IllegalArgumentException if the supplied method cannot be used to create a 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(); + } + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseNetworkAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseNetworkAdapter.java new file mode 100644 index 00000000000..32865850062 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseNetworkAdapter.java @@ -0,0 +1,326 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if {@code network} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if either one of {@code network} or {@code vertexOrderMethod} is {@code null} + */ + 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 {@code v} must not be equal + * to any other vertex in the graph. More formally, the graph must not contain any vertex + * {@code v2} such that {@code 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 {@code 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 {@code e2} such that {@code 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); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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 + * + * @throws IllegalArgumentException if the supplied method cannot be used to create a 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(); + } + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseValueGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseValueGraphAdapter.java new file mode 100644 index 00000000000..6442a88a186 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseValueGraphAdapter.java @@ -0,0 +1,373 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if either one of {@code valueGraph} or {@code valueConverter} is {@code null} + */ + 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 + * + * @throws NullPointerException if either one of {@code valueGraph} or {@code valueConverter} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if any one of {@code valueGraph}, {@code valueConverter}, or {@code vertexOrderMethod} + * is {@code null} + */ + 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 {@code v} must not be equal + * to any other vertex in the graph. More formally, the graph must not contain any vertex + * {@code v2} such that {@code 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 {@code 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 {@code e2} such that {@code 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)); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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 + * + * @throws IllegalArgumentException if the supplied method cannot be used to create a 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(); + } + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrder.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrder.java new file mode 100644 index 00000000000..60dfd5261a5 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrder.java @@ -0,0 +1,157 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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++; + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrderMethod.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrderMethod.java new file mode 100644 index 00000000000..bd1620a9526 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrderMethod.java @@ -0,0 +1,135 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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, + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableDoubleValueGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableDoubleValueGraphAdapter.java new file mode 100644 index 00000000000..7b44a9ac56a --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableDoubleValueGraphAdapter.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableGraphAdapter.java new file mode 100644 index 00000000000..1890ef1cde1 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableGraphAdapter.java @@ -0,0 +1,245 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if {@code graph} is {@code null} + */ + public ImmutableGraphAdapter(ImmutableGraph graph) + { + super(graph); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public EndpointPair addEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public V addVertex() + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public EndpointPair removeEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeEdge(EndpointPair e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @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) { + throw new RuntimeException(e); + } + } + + 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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableNetworkAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableNetworkAdapter.java new file mode 100644 index 00000000000..db46735a6c9 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableNetworkAdapter.java @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E addEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addEdge(V sourceVertex, V targetVertex, E e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public V addVertex() + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public E removeEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeEdge(E e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + @Override + public double getEdgeWeight(E e) + { + return Graph.DEFAULT_EDGE_WEIGHT; + } + + /** + * @throws UnsupportedOperationException always + */ + @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) { + throw new RuntimeException(e); + } + } + + 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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapter.java new file mode 100644 index 00000000000..eec66c43a23 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapter.java @@ -0,0 +1,282 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if either one of {@code valueGraph} or {@code valueConverter} is {@code null} + */ + public ImmutableValueGraphAdapter( + ImmutableValueGraph valueGraph, ToDoubleFunction valueConverter) + { + super(valueGraph, valueConverter); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public EndpointPair addEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public V addVertex() + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean addVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public EndpointPair removeEdge(V sourceVertex, V targetVertex) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeEdge(EndpointPair e) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public boolean removeVertex(V v) + { + throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); + } + + /** + * @throws UnsupportedOperationException always + */ + @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) { + throw new RuntimeException(e); + } + } + + 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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableDoubleValueGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableDoubleValueGraphAdapter.java new file mode 100644 index 00000000000..83f8d3cd8bd --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableDoubleValueGraphAdapter.java @@ -0,0 +1,116 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if {@code valueGraph} is {@code null} + */ + 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 + * + * @throws NullPointerException if {@code valueGraph} is {@code null} + */ + public MutableDoubleValueGraphAdapter( + MutableValueGraph valueGraph, Supplier vertexSupplier, + Supplier> edgeSupplier) + { + super( + valueGraph, Graph.DEFAULT_EDGE_WEIGHT, (ToDoubleFunction & Serializable) x -> x, + vertexSupplier, edgeSupplier); + } + + /** + * @throws IllegalArgumentException if {@code e} is not an edge of this graph + * @throws NullPointerException {@inheritDoc} + */ + @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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableGraphAdapter.java new file mode 100644 index 00000000000..7e308c1e562 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableGraphAdapter.java @@ -0,0 +1,315 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if {@code graph} is {@code null} + */ + 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 + * + * @throws NullPointerException if {@code graph} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if either one of {@code graph} or {@code vertexOrderMethod} is {@code null} + */ + public MutableGraphAdapter( + MutableGraph graph, Supplier vertexSupplier, Supplier> edgeSupplier, + ElementOrderMethod vertexOrderMethod) + { + super(graph, vertexSupplier, edgeSupplier, vertexOrderMethod); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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 {@code null} or must respect the source and target + * vertices that are provided as parameters. + * + * @throws IllegalArgumentException if either {@code sourceVertex} is not equal to node U + * of {@code e} or {@code targetVertex} is not equal to node V + * of {@code e}, or if the underlying graph disallows self loops + * @throws NullPointerException if either one of {@code sourceVertex} or {@code targetVertex} is {@code null} + */ + @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; + } + + /** + * @throws UnsupportedOperationException if this graph was not initialized with a vertex supplier + */ + @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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableNetworkAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableNetworkAdapter.java new file mode 100644 index 00000000000..9e7ce61ed41 --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableNetworkAdapter.java @@ -0,0 +1,315 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if {@code network} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if either one of {@code network} or {@code vertexOrderMethod} is {@code null} + */ + public MutableNetworkAdapter( + MutableNetwork network, Supplier vertexSupplier, Supplier edgeSupplier, + ElementOrderMethod vertexOrderMethod) + { + super(network, vertexSupplier, edgeSupplier, vertexOrderMethod); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException if the graph was not initialized with an edge supplier + */ + @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; + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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; + } + + /** + * @throws UnsupportedOperationException if this graph was not initialized with a vertex supplier + */ + @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); + } + + /** + * @throws UnsupportedOperationException always + */ + @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) { + throw new RuntimeException(e); + } + } + + 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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableValueGraphAdapter.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableValueGraphAdapter.java new file mode 100644 index 00000000000..80d3b434f9b --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableValueGraphAdapter.java @@ -0,0 +1,386 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 + * + * @throws NullPointerException if any one of {@code valueGraph}, {@code defaultValue} or + * {@code valueConverter} is {@code null} + */ + 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 + * + * @throws NullPointerException if any one of {@code valueGraph}, {@code defaultValue} or + * {@code valueConverter} is {@code null} + */ + 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. + * + * @throws IllegalArgumentException if the supplied {@code vertexOrderMethod} cannot be used to create a vertex order + * @throws NullPointerException if any one of {@code valueGraph}, {@code defaultValue}, {@code valueConverter}, + * or {@code vertexOrderMethod} is {@code null} + */ + 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); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + @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 {@code null} or must respect the source and target + * vertices that are provided as parameters. + * + * @throws IllegalArgumentException if either {@code sourceVertex} is not equal to node U + * of {@code e} or {@code targetVertex} is not equal to node V + * of {@code e}, or if the underlying graph disallows self loops + * @throws NullPointerException if either one of {@code sourceVertex} or {@code targetVertex} is {@code null} + */ + @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; + } + + /** + * @throws UnsupportedOperationException if this graph was not initialized with a vertex supplier + */ + @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 NullPointerException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + */ + @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) { + throw new RuntimeException(e); + } + } + + 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); + } + +} diff --git a/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/package-info.java b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/package-info.java new file mode 100644 index 00000000000..ade06ffee6a --- /dev/null +++ b/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/package-info.java @@ -0,0 +1,24 @@ +/* + * (C) Copyright 2018-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Guava adapters allow you + * to run JGraphT algorithms directly against + * Guava's common.graph data structures. + */ +package org.jgrapht.graph.guava; diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableGraphAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableGraphAdapterTest.java new file mode 100644 index 00000000000..d52d79e72f1 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableGraphAdapterTest.java @@ -0,0 +1,230 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import com.google.common.graph.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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")); + + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableNetworkAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableNetworkAdapterTest.java new file mode 100644 index 00000000000..563b17ac35e --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableNetworkAdapterTest.java @@ -0,0 +1,231 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapterTest.java new file mode 100644 index 00000000000..af1390dffa5 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/ImmutableValueGraphAdapterTest.java @@ -0,0 +1,363 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; +import java.util.function.*; + +import com.google.common.graph.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + + @SuppressWarnings("unchecked") 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); + + EndpointPair endPoints = EndpointPair.ordered("v1", "v2"); + assertThrows(UnsupportedOperationException.class, () -> g.setEdgeWeight(endPoints, 1.0)); + } + + /** + * 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); + + EndpointPair endPoints = EndpointPair.ordered("v1", "v2"); + assertThrows(UnsupportedOperationException.class, () -> g.setEdgeWeight(endPoints, 1.0)); + } + + /** + * 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); + + @SuppressWarnings("unchecked") Graph> graph = + new ImmutableValueGraphAdapter<>( + immutableValueGraph, (ToDoubleFunction & Serializable) MyValue::getValue); + + assertEquals(5.0, graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 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)); + + @SuppressWarnings("unchecked") 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)); + + @SuppressWarnings("unchecked") 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 final double value; + + public MyValue(double value) + { + this.value = value; + } + + public double getValue() + { + return value; + } + + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java new file mode 100644 index 00000000000..5170829e800 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java @@ -0,0 +1,325 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableNetworkAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableNetworkAdapterTest.java new file mode 100644 index 00000000000..8a4ba5a41e1 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableNetworkAdapterTest.java @@ -0,0 +1,351 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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)); + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableValueGraphAdapterTest.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableValueGraphAdapterTest.java new file mode 100644 index 00000000000..1a20742a609 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableValueGraphAdapterTest.java @@ -0,0 +1,449 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; +import java.util.function.*; + +import com.google.common.graph.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Check Incoming/Outgoing edges in directed and undirected graphs. + * + * @author Dimitrios Michail + */ +@SuppressWarnings("unchecked") +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 + EndpointPair endPoints = EndpointPair.ordered("v1", "v2"); + assertThrows(UnsupportedOperationException.class, () -> g.setEdgeWeight(endPoints, 1.0)); + } + + /** + * 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(5.0, graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); + + valueGraph.putEdgeValue("v1", "v2", new MyValue(9.0)); + + assertEquals(9.0, graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 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")); + assertEquals(5, g2.vertexSet().size()); + assertEquals(6, g2.edgeSet().size()); + 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")); + assertEquals(3, g2.vertexSet().size()); + assertEquals(3, g2.edgeSet().size()); + 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)); + } + +} diff --git a/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/SerializationTestUtils.java b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/SerializationTestUtils.java new file mode 100644 index 00000000000..162adccff21 --- /dev/null +++ b/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/SerializationTestUtils.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2003-2023, 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; + } +} diff --git a/jgrapht-io/pom.xml b/jgrapht-io/pom.xml new file mode 100644 index 00000000000..2989ca7c22e --- /dev/null +++ b/jgrapht-io/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + jgrapht-io + JGraphT - I/O + + ${project.parent.basedir} + 4.13.2 + + + + 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 + ${antlr4.version} + + + antlr + + antlr4 + + + + + + + com.google.code.maven-replacer-plugin + replacer + + + 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 + + + + + + + com.google.code.maven-replacer-plugin + + + replacer + + + [1.5.3,) + + + replace + + + + + false + + + + + + + + + + + + + ${project.groupId} + jgrapht-core + + + org.xmlunit + xmlunit-core + test + + + org.junit.jupiter + junit-jupiter + test + + + org.antlr + antlr4-runtime + ${antlr4.version} + + + org.apache.commons + commons-text + + + diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/io/CSV.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/io/CSV.g4 new file mode 100644 index 00000000000..818d8986bb5 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/io/CSV.g4 @@ -0,0 +1,53 @@ +/* + * (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 : '"' ('""'|~'"')* '"' ; diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/io/DOT.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/io/DOT.g4 new file mode 100644 index 00000000000..a53696d36d2 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/io/DOT.g4 @@ -0,0 +1,173 @@ +/* + * (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 + ; + diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/io/Gml.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/io/Gml.g4 new file mode 100644 index 00000000000..661890693b2 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/io/Gml.g4 @@ -0,0 +1,58 @@ +/* + * (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 + ; + \ No newline at end of file diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/io/Json.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/io/Json.g4 new file mode 100644 index 00000000000..faefe632611 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/io/Json.g4 @@ -0,0 +1,72 @@ +/** 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 + ; diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/nio/csv/CSV.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/csv/CSV.g4 new file mode 100644 index 00000000000..818d8986bb5 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/csv/CSV.g4 @@ -0,0 +1,53 @@ +/* + * (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 : '"' ('""'|~'"')* '"' ; diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/nio/dot/DOT.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/dot/DOT.g4 new file mode 100644 index 00000000000..630151310f4 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/dot/DOT.g4 @@ -0,0 +1,172 @@ +/* + * (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 + : ~["] + | '\\"' + ; + +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 + ; + diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/nio/gml/Gml.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/gml/Gml.g4 new file mode 100644 index 00000000000..661890693b2 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/gml/Gml.g4 @@ -0,0 +1,58 @@ +/* + * (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 + ; + \ No newline at end of file diff --git a/jgrapht-io/src/main/antlr4/org/jgrapht/nio/json/Json.g4 b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/json/Json.g4 new file mode 100644 index 00000000000..faefe632611 --- /dev/null +++ b/jgrapht-io/src/main/antlr4/org/jgrapht/nio/json/Json.g4 @@ -0,0 +1,72 @@ +/** 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 + ; diff --git a/jgrapht-io/src/main/java/module-info.java b/jgrapht-io/src/main/java/module-info.java new file mode 100644 index 00000000000..992e9e00f88 --- /dev/null +++ b/jgrapht-io/src/main/java/module-info.java @@ -0,0 +1,43 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides I/O extensions for the JGraphT library. + * + * @since 1.5.0 + */ +module 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; +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/Attribute.java b/jgrapht-io/src/main/java/org/jgrapht/nio/Attribute.java new file mode 100644 index 00000000000..8a21e8f0ed5 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/Attribute.java @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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(); + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/AttributeType.java b/jgrapht-io/src/main/java/org/jgrapht/nio/AttributeType.java new file mode 100644 index 00000000000..a7130a36c59 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/AttributeType.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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"); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/BaseEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/BaseEventDrivenImporter.java new file mode 100644 index 00000000000..e6a709db350 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/BaseEventDrivenImporter.java @@ -0,0 +1,372 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/BaseExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/BaseExporter.java new file mode 100644 index 00000000000..0f5d70fccd4 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/BaseExporter.java @@ -0,0 +1,288 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/DefaultAttribute.java b/jgrapht-io/src/main/java/org/jgrapht/nio/DefaultAttribute.java new file mode 100644 index 00000000000..5249b6a8c0b --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/DefaultAttribute.java @@ -0,0 +1,178 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 final 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 + 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; + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/EventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/EventDrivenImporter.java new file mode 100644 index 00000000000..26670edbb39 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/EventDrivenImporter.java @@ -0,0 +1,209 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/ExportException.java b/jgrapht-io/src/main/java/org/jgrapht/nio/ExportException.java new file mode 100644 index 00000000000..1e9c06f8a87 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/ExportException.java @@ -0,0 +1,82 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/GraphExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/GraphExporter.java new file mode 100644 index 00000000000..10fa9acdd29 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/GraphExporter.java @@ -0,0 +1,78 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 (Writer writer = new FileWriter(file, StandardCharsets.UTF_8)) { + exportGraph(g, writer); + } catch (IOException e) { + throw new ExportException(e); + } + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/GraphImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/GraphImporter.java new file mode 100644 index 00000000000..037988733c9 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/GraphImporter.java @@ -0,0 +1,78 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 (Reader reader = new FileReader(file, StandardCharsets.UTF_8)) { + importGraph(g, reader); + } catch (IOException e) { + throw new ImportException(e); + } + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/ImportEvent.java b/jgrapht-io/src/main/java/org/jgrapht/nio/ImportEvent.java new file mode 100644 index 00000000000..0553131b059 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/ImportEvent.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/ImportException.java b/jgrapht-io/src/main/java/org/jgrapht/nio/ImportException.java new file mode 100644 index 00000000000..6c150eedb89 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/ImportException.java @@ -0,0 +1,84 @@ +/* + * (C) Copyright 2015-2023, 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); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/IntegerIdProvider.java b/jgrapht-io/src/main/java/org/jgrapht/nio/IntegerIdProvider.java new file mode 100644 index 00000000000..b7f19e31346 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/IntegerIdProvider.java @@ -0,0 +1,68 @@ +/* + * (C) Copyright 2005-2023, 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); + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVEventDrivenImporter.java new file mode 100644 index 00000000000..d2101aedc86 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVEventDrivenImporter.java @@ -0,0 +1,494 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 | ParseCancellationException | IllegalArgumentException e) { + throw new ImportException("Failed to import CSV graph: " + e.getMessage(), e); + } + } + + 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(); + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVExporter.java new file mode 100644 index 00000000000..c4a21065046 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVExporter.java @@ -0,0 +1,287 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVFormat.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVFormat.java new file mode 100644 index 00000000000..0bcf2ce0549 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVFormat.java @@ -0,0 +1,194 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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, + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVImporter.java new file mode 100644 index 00000000000..244aa78d904 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVImporter.java @@ -0,0 +1,307 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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())); + } + }; + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/DSVUtils.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/DSVUtils.java new file mode 100644 index 00000000000..17cb51884c8 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/DSVUtils.java @@ -0,0 +1,103 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/VisioExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/VisioExporter.java new file mode 100644 index 00000000000..e34ad447516 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/VisioExporter.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2003-2023, 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"); + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/csv/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/package-info.java new file mode 100644 index 00000000000..fbcd4dd7df3 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/csv/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * CSV importers/exporters + */ +package org.jgrapht.nio.csv; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporter.java new file mode 100644 index 00000000000..fe84dc48bc7 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporter.java @@ -0,0 +1,259 @@ +/* + * (C) Copyright 2010-2023, 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); + } + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSExporter.java new file mode 100644 index 00000000000..315ba420852 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSExporter.java @@ -0,0 +1,172 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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"); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSFormat.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSFormat.java new file mode 100644 index 00000000000..78ec43da959 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSFormat.java @@ -0,0 +1,131 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSImporter.java new file mode 100644 index 00000000000..9b10b73c20e --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSImporter.java @@ -0,0 +1,217 @@ +/* + * (C) Copyright 2010-2023, 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; + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/package-info.java new file mode 100644 index 00000000000..61bb0f27d7b --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * DIMACS Challenges importers/exporters + */ +package org.jgrapht.nio.dimacs; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTEventDrivenImporter.java new file mode 100644 index 00000000000..57403ce6a2f --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTEventDrivenImporter.java @@ -0,0 +1,805 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 unescapeId; + + 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("\\\"", "\""); + unescapeId = 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 = unescapeId.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; + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTExporter.java new file mode 100644 index 00000000000..c6ffb7569a4 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTExporter.java @@ -0,0 +1,277 @@ +/* + * (C) Copyright 2006-2023, 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('='); + Attribute attribute = attr.getValue(); + out.print(attribute.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 {@code 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; + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTImporter.java new file mode 100644 index 00000000000..117c98e74c7 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTImporter.java @@ -0,0 +1,323 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + }; + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTUtils.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTUtils.java new file mode 100644 index 00000000000..98615b0b07e --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTUtils.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright 2003-2023, 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 {@code true} if it is valid; {@code 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(); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/dot/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/package-info.java new file mode 100644 index 00000000000..e46e6762fbc --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/dot/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * DOT importers/exporters + */ +package org.jgrapht.nio.dot; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFAttributeType.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFAttributeType.java new file mode 100644 index 00000000000..2c4d16c3cda --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFAttributeType.java @@ -0,0 +1,81 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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"); + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFExporter.java new file mode 100644 index 00000000000..bbe8dbff07c --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFExporter.java @@ -0,0 +1,630 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporter.java new file mode 100644 index 00000000000..f0d3abf1f69 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporter.java @@ -0,0 +1,496 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 e) { + throw new ImportException("Failed to parse GEXF", e); + } + } + + 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); + + // disable DOCTYPE declaration + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + + 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 e) { + throw new ImportException("Failed to parse GEXF", e); + } + } + + private static final List GRAPH_ATTRS = + List.of("defaultedgetype", "timeformat", "mode", "start", "end"); + private static final List NODE_ATTRS = List.of("label", "pid"); + private static final List EDGE_ATTRS = List.of("type", "label"); + + // content handler + private class GEXFHandler + extends DefaultHandler + { + private static final String GRAPH = "graph"; + + 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 EDGE_WEIGHT = "weight"; + + 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; + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFImporter.java new file mode 100644 index 00000000000..eacf13225de --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFImporter.java @@ -0,0 +1,294 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/package-info.java new file mode 100644 index 00000000000..0e13f9ae35c --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/package-info.java @@ -0,0 +1,24 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph Exchange XML Format (GEXF) importers/exporters. + * + * See here for a description of the format. + */ +package org.jgrapht.nio.gexf; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlEventDrivenImporter.java new file mode 100644 index 00000000000..8ec135ec7f5 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlEventDrivenImporter.java @@ -0,0 +1,425 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 | ParseCancellationException | IllegalArgumentException e) { + throw new ImportException("Failed to import gml graph: " + e.getMessage(), e); + } + } + + 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; + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlExporter.java new file mode 100644 index 00000000000..49116185091 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlExporter.java @@ -0,0 +1,365 @@ +/* + * (C) Copyright 2006-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 TAB3 = "\t\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; + + protected Optional>> vertexGraphicsAttributeProvider; + + /** + * 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 outputs graphics section attributes for nodes + */ + EXPORT_CUSTOM_VERTEX_GRAPHICS_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<>(); + this.vertexGraphicsAttributeProvider = Optional.empty(); + } + + /** + * 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); + } + } + + /** + * Set node craphics section provider + * @param vertexGraphicsAttributeProvider the graphics section attributes provider + */ + public void setVertexGraphicsAttributeProvider(Function> vertexGraphicsAttributeProvider) + { + this.vertexGraphicsAttributeProvider = Optional.ofNullable(vertexGraphicsAttributeProvider); + } + + + protected Optional> getVertexGraphicsAttributes(V v) + { + return vertexGraphicsAttributeProvider.map(x -> x.apply(v)); + } + + 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) + { + exportAttribute(out, key, attribute, TAB2); + } + + private void exportAttribute(PrintWriter out, String key, Attribute attribute, String prefix) + { + AttributeType type = attribute.getType(); + switch (type) { + case INT: + out.println(prefix + key + DELIM + Integer.valueOf(attribute.getValue())); + break; + case LONG: + out.println(prefix + key + DELIM + Long.valueOf(attribute.getValue())); + break; + case FLOAT: + out.println(prefix + key + DELIM + Float.valueOf(attribute.getValue())); + break; + case DOUBLE: + out.println(prefix + key + DELIM + Double.valueOf(attribute.getValue())); + break; + default: + out.println(prefix + 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); + boolean exportCustomNodeGraphicsAttributes = parameters.contains(Parameter.EXPORT_CUSTOM_VERTEX_GRAPHICS_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); + }); + }); + } + if (exportCustomNodeGraphicsAttributes) { + getVertexGraphicsAttributes(from).ifPresent(graphicsAttributes -> { + out.println(TAB2 + "graphics" + System.lineSeparator() + TAB2 + "["); + graphicsAttributes.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"); + } + + exportAttribute(out, customAttributeKey, customAttributeValue, TAB3); + }); + out.println(TAB2 + "]"); + }); + } + + 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 + "]"); + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlImporter.java new file mode 100644 index 00000000000..57ad7691fec --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlImporter.java @@ -0,0 +1,255 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/gml/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/package-info.java new file mode 100644 index 00000000000..abfe4b477d7 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/gml/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * GML importers/exporters + */ +package org.jgrapht.nio.gml; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6EventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6EventDrivenImporter.java new file mode 100644 index 00000000000..cb4cc590ff4 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6EventDrivenImporter.java @@ -0,0 +1,299 @@ +/* + * (C) Copyright 2017-2023, 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. + * + *

    + * {@code ":?@MnDA\oi"}
    + * 
    + * + * may result in undefined behavior. This should have been: + * + *
    + * {@code ":?@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; + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Exporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Exporter.java new file mode 100644 index 00000000000..8f3ffe8503f --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Exporter.java @@ -0,0 +1,235 @@ +/* + * (C) Copyright 2017-2023, 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; + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Importer.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Importer.java new file mode 100644 index 00000000000..fdcf02e113e --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Importer.java @@ -0,0 +1,188 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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. + * + *

    + * {@code ":?@MnDA\oi"}
    + * 
    + * + * may result in undefined behavior. This should have been: + * + *
    + * {@code ":?@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); + }; + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/package-info.java new file mode 100644 index 00000000000..85e81c009ea --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Graph6, sparse6 and digraph6 importers/exporters + */ +package org.jgrapht.nio.graph6; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLEventDrivenImporter.java new file mode 100644 index 00000000000..533a8d26e24 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLEventDrivenImporter.java @@ -0,0 +1,628 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 e) { + throw new ImportException("Failed to parse GraphML", e); + } + } + + 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 e) { + throw new ImportException("Failed to parse GraphML", e); + } + } + + // 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(); + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLExporter.java new file mode 100644 index 00000000000..5c4f0f038a1 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLExporter.java @@ -0,0 +1,585 @@ +/* + * (C) Copyright 2006-2023, 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; + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLImporter.java new file mode 100644 index 00000000000..98a6436e2ed --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLImporter.java @@ -0,0 +1,334 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporter.java new file mode 100644 index 00000000000..a84c1d7bd26 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporter.java @@ -0,0 +1,237 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporter.java new file mode 100644 index 00000000000..1fdd19ea5af --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporter.java @@ -0,0 +1,532 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 e) { + throw new ImportException("Failed to parse GraphML", e); + } + } + + 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 e) { + throw new ImportException("Failed to parse GraphML", e); + } + } + + // 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; + } + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLImporter.java new file mode 100644 index 00000000000..150403c97bb --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLImporter.java @@ -0,0 +1,336 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/package-info.java new file mode 100644 index 00000000000..51f82649a82 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * GraphML importers/exporters + */ +package org.jgrapht.nio.graphml; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONEventDrivenImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONEventDrivenImporter.java new file mode 100644 index 00000000000..e23b63c5b3c --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONEventDrivenImporter.java @@ -0,0 +1,503 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.Map.Entry; +import java.util.Objects; +import java.util.UUID; + +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> +{ + /** + * Default name for the vertices collection + */ + public static final String DEFAULT_VERTICES_COLLECTION_NAME = "nodes"; + /** + * Default name for the edges collection + */ + public static final String DEFAULT_EDGES_COLLECTION_NAME = "edges"; + + private boolean notifyVertexAttributesOutOfOrder; + private boolean notifyEdgeAttributesOutOfOrder; + private String verticesCollectionName = DEFAULT_VERTICES_COLLECTION_NAME; + private String edgesCollectionName = DEFAULT_EDGES_COLLECTION_NAME; + + /** + * 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; + } + + /** + * Get the name used for the vertices collection in the file. + * + * @return the name used for the vertices collection in the file. + */ + public String getVerticesCollectionName() + { + return verticesCollectionName; + } + + /** + * Set the name used for the vertices collection in the file. + * + * @param verticesCollectionName the name + */ + public void setVerticesCollectionName(String verticesCollectionName) + { + this.verticesCollectionName = Objects.requireNonNull(verticesCollectionName); + } + + /** + * Get the name used for the edges collection in the file. + * + * @return the name used for the edges collection in the file. + */ + public String getEdgesCollectionName() + { + return edgesCollectionName; + } + + /** + * Set the name used for the edges collection in the file. + * + * @param edgesCollectionName the name + */ + public void setEdgesCollectionName(String edgesCollectionName) + { + this.edgesCollectionName = Objects.requireNonNull(edgesCollectionName); + } + + @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 | ParseCancellationException | IllegalArgumentException e) { + throw new ImportException("Failed to import json graph: " + e.getMessage(), e); + } + } + + 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 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 (verticesCollectionName.equals(name)) { + insideNodes = true; + } else if (edgesCollectionName.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 (verticesCollectionName.equals(name)) { + insideNodes = false; + } else if (edgesCollectionName.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"); + } + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONExporter.java new file mode 100644 index 00000000000..cfca0375fa5 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONExporter.java @@ -0,0 +1,300 @@ +/* + * (C) Copyright 2019-2023, Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.PrintWriter; +import java.io.Writer; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import org.apache.commons.text.StringEscapeUtils; +import org.jgrapht.Graph; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.AttributeType; +import org.jgrapht.nio.BaseExporter; +import org.jgrapht.nio.GraphExporter; +import org.jgrapht.nio.IntegerIdProvider; + +/** + * Exports a graph using JSON. + * + *

    + * The output is one object which contains: + *

      + *
    • A member named {@code nodes} whose value is an array of nodes. + *
    • A member named {@code edges} whose value is an array of edges. + *
    • Two members named {@code creator} and {@code 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 +{ + /** + * Default name for the vertices collection + */ + public static final String DEFAULT_VERTICES_COLLECTION_NAME = "nodes"; + /** + * Default name for the edges collection + */ + + public static final String DEFAULT_EDGES_COLLECTION_NAME = "edges"; + + private static final String CREATOR = "JGraphT JSON Exporter"; + private static final String VERSION = "1"; + private String verticesCollectionName = DEFAULT_VERTICES_COLLECTION_NAME; + private String edgesCollectionName = DEFAULT_EDGES_COLLECTION_NAME; + + /** + * 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); + } + + /** + * Get the name used for the vertices collection in the file. + * + * @return the name used for the vertices collection in the file. + */ + public String getVerticesCollectionName() + { + return verticesCollectionName; + } + + /** + * Set the name used for the vertices collection in the file. + * + * @param verticesCollectionName the name + */ + public void setVerticesCollectionName(String verticesCollectionName) + { + this.verticesCollectionName = Objects.requireNonNull(verticesCollectionName); + } + + /** + * Get the name used for the edges collection in the file. + * + * @return the name used for the edges collection in the file. + */ + public String getEdgesCollectionName() + { + return edgesCollectionName; + } + + /** + * Set the name used for the edges collection in the file. + * + * @param edgesCollectionName the name + */ + public void setEdgesCollectionName(String edgesCollectionName) + { + this.edgesCollectionName = Objects.requireNonNull(edgesCollectionName); + } + + @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(verticesCollectionName)); + 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(edgesCollectionName)); + 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) + "\""; + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONImporter.java new file mode 100644 index 00000000000..a7b9f3f2e00 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONImporter.java @@ -0,0 +1,421 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.Objects; +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"; + /** + * Default name for the vertices collection + */ + public static final String DEFAULT_VERTICES_COLLECTION_NAME = "nodes"; + /** + * Default name for the edges collection + */ + public static final String DEFAULT_EDGES_COLLECTION_NAME = "edges"; + + private Function vertexFactory; + private BiFunction, V> vertexWithAttributesFactory; + private Function, E> edgeWithAttributesFactory; + private String verticesCollectionName = DEFAULT_VERTICES_COLLECTION_NAME; + private String edgesCollectionName = DEFAULT_EDGES_COLLECTION_NAME; + + /** + * 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); + genericImporter.setVerticesCollectionName(verticesCollectionName); + genericImporter.setEdgesCollectionName(edgesCollectionName); + + 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; + } + + /** + * Get the name used for the vertices collection in the file. + * + * @return the name used for the vertices collection in the file. + */ + public String getVerticesCollectionName() + { + return verticesCollectionName; + } + + /** + * Set the name used for the vertices collection in the file. + * + * @param verticesCollectionName the name + */ + public void setVerticesCollectionName(String verticesCollectionName) + { + this.verticesCollectionName = Objects.requireNonNull(verticesCollectionName); + } + + /** + * Get the name used for the edges collection in the file. + * + * @return the name used for the edges collection in the file. + */ + public String getEdgesCollectionName() + { + return edgesCollectionName; + } + + /** + * Set the name used for the edges collection in the file. + * + * @param edgesCollectionName the name + */ + public void setEdgesCollectionName(String edgesCollectionName) + { + this.edgesCollectionName = Objects.requireNonNull(edgesCollectionName); + } + + 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); + } + }; + + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/json/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/json/package-info.java new file mode 100644 index 00000000000..6ce4bff92c4 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/json/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Json importers/exporters + */ +package org.jgrapht.nio.json; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/LemonExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/LemonExporter.java new file mode 100644 index 00000000000..3276c4c53d7 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/LemonExporter.java @@ -0,0 +1,183 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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(); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/package-info.java new file mode 100644 index 00000000000..f933bd3c1d4 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Lemon input/output. + */ +package org.jgrapht.nio.lemon; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/MatrixExporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/MatrixExporter.java new file mode 100644 index 00000000000..11d3e9805c8 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/MatrixExporter.java @@ -0,0 +1,256 @@ +/* + * (C) Copyright 2005-2023, 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(); + } + +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/package-info.java new file mode 100644 index 00000000000..a4afab25b5c --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Matrix input/output + */ +package org.jgrapht.nio.matrix; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/package-info.java new file mode 100644 index 00000000000..5c525d07461 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Importers/Exporters for various graph formats. + */ +package org.jgrapht.nio; diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/TSPLIBImporter.java b/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/TSPLIBImporter.java new file mode 100644 index 00000000000..dcf7ebaf921 --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/TSPLIBImporter.java @@ -0,0 +1,1041 @@ +/* + * (C) Copyright 2020-2023, 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.ArrayList; +import java.util.function.*; +import java.util.regex.*; +import java.util.stream.*; + +import static java.util.Arrays.*; + +/** + * 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 = stream(coordinates).boxed().toArray(Double[]::new); + 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 static final Pattern WHITE_SPACE = Pattern.compile("[ \t]+"); + + private Node parseNode(String line) + { + String[] elements = WHITE_SPACE.split(line); + 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) + { + value = extractValueBeforeWhitespace(value); + 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) + { + valueStr = extractValueBeforeWhitespace(valueStr); + try { + return Integer.valueOf(valueStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid " + valueType + " integer value <" + valueStr + ">", e); + } + } + + public String extractValueBeforeWhitespace(String value) + { + return WHITE_SPACE.split(value.strip(), 2)[0]; // discard everything after first white-space + } + + // 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.strip(); + 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].strip().toUpperCase(); + } + + private String getValue(String[] keyValue) + { + if (keyValue.length < 2) { + throw new IllegalStateException("Missing value for key " + getKey(keyValue)); + } + return keyValue[1].strip(); + } + + 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; + } +} diff --git a/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/package-info.java b/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/package-info.java new file mode 100644 index 00000000000..9d1b1c8c48f --- /dev/null +++ b/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2020-2024, 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 + */ + +/** + * TSPLIB95 importers/exporters + */ +package org.jgrapht.nio.tsplib; diff --git a/jgrapht-io/src/main/resources/gexf.xsd b/jgrapht-io/src/main/resources/gexf.xsd new file mode 100644 index 00000000000..a2faf48b3c6 --- /dev/null +++ b/jgrapht-io/src/main/resources/gexf.xsd @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tree + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Datatypes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jgrapht-io/src/main/resources/graphml.xsd b/jgrapht-io/src/main/resources/graphml.xsd new file mode 100644 index 00000000000..f8d3325c034 --- /dev/null +++ b/jgrapht-io/src/main/resources/graphml.xsd @@ -0,0 +1,1387 @@ + + + + + + + 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). + + + + + + + + + + + + diff --git a/jgrapht-io/src/main/resources/viz.xsd b/jgrapht-io/src/main/resources/viz.xsd new file mode 100644 index 00000000000..29b9d33443b --- /dev/null +++ b/jgrapht-io/src/main/resources/viz.xsd @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jgrapht-io/src/main/resources/xlink.xsd b/jgrapht-io/src/main/resources/xlink.xsd new file mode 100644 index 00000000000..f30e2c85911 --- /dev/null +++ b/jgrapht-io/src/main/resources/xlink.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/IntegerIdProviderTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/IntegerIdProviderTest.java new file mode 100644 index 00000000000..475ed116e6a --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/IntegerIdProviderTest.java @@ -0,0 +1,52 @@ +/* + * (C) Copyright 2019-2023, 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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVExporterTest.java new file mode 100644 index 00000000000..8c80bcb4f8c --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVExporterTest.java @@ -0,0 +1,496 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@link CSVExporter}. + * + * @author Dimitrios Michail + */ +public class CSVExporterTest +{ + // ~ Static fields/initializers + // --------------------------------------------- + + private static final String NL = System.lineSeparator(); + 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()); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVImporterTest.java new file mode 100644 index 00000000000..8122296addf --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVImporterTest.java @@ -0,0 +1,685 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class CSVImporterTest +{ + private static final String NL = System.lineSeparator(); + + 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")); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/csv/DSVUtilsTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/DSVUtilsTest.java new file mode 100644 index 00000000000..56e1753a731 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/csv/DSVUtilsTest.java @@ -0,0 +1,74 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.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, ';')); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporterTest.java new file mode 100644 index 00000000000..19623acee41 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporterTest.java @@ -0,0 +1,265 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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(11, count); + }); + 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(edges[i][0], e.getFirst()); + assertEquals(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(11, count); + }); + 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(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(11, count); + }); + 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(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(11, count); + }); + 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(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(11, count); + }); + 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(edge[2], e.getThird()); + i++; + } + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSExporterTest.java new file mode 100644 index 00000000000..0a63b6b4303 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSExporterTest.java @@ -0,0 +1,255 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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.lineSeparator(); + + // @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); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSImporterTest.java new file mode 100644 index 00000000000..eae93a47fa9 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSImporterTest.java @@ -0,0 +1,231 @@ +/* + * (C) Copyright 2016-2023, 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.jupiter.api.*; + +import java.io.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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<>(); + importer.importGraph(g, new InputStreamReader(in, UTF_8)); + + 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(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); + importer.importGraph( + graph, new InputStreamReader( + new ByteArrayInputStream(input.getBytes(UTF_8)), UTF_8)); + + 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(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(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(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(UTF_8)), DefaultEdge.class, + false); + fail("No!"); + } catch (ImportException e) { + } + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTExporterTest.java new file mode 100644 index 00000000000..39a850e32b4 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTExporterTest.java @@ -0,0 +1,218 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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.lineSeparator(); + + 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<>(String::valueOf); + + 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); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + assertThrows(RuntimeException.class, () -> exporter.exportGraph(graph, out)); + } + } + + @Test + public void testQuotedNodeIDs() + { + DOTExporter exporter = new DOTExporter<>(String::valueOf); + + 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); + + assertTrue(outputWriter.toString().contains("label=\"\\\"abc\\\"\"")); + } + + @Test + public void testNodeHtmlLabelFromAttribute() + { + DOTExporter exporter = new DOTExporter<>(String::valueOf); + + 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); + + assertTrue(outputWriter.toString().contains("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(() -> 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); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter1Test.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter1Test.java new file mode 100644 index 00000000000..9d98daae065 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter1Test.java @@ -0,0 +1,577 @@ +/* + * (C) Copyright 2015-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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(2, result.vertexSet().size(), "wrong size of vertexSet"); + assertEquals(1, result.edgeSet().size(), "wrong size of edgeSet"); + + 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(2, result.vertexSet().size(), "wrong size of vertexSet"); + assertEquals(1, result.edgeSet().size(), "wrong size of edgeSet"); + for (String v : result.vertexSet()) { + if ("0".equals(v)) { + assertEquals(2, attrs.get(v).size(), "wrong number of attributes"); + } else { + assertEquals(1, attrs.get(v).size(), "attributes are populated"); + } + } + + 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(1, result.vertexSet().size(), "wrong size of vertexSet"); + assertEquals(0, result.edgeSet().size(), "wrong size of edgeSet"); + } + + @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(1, result.vertexSet().size(), "wrong size of vertexSet"); + assertEquals(0, result.edgeSet().size(), "wrong size of edgeSet"); + 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(2, result.vertexSet().size(), "wrong size of vertexSet"); + assertEquals(0, result.edgeSet().size(), "wrong size of edgeSet"); + } + + @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()); + } + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter2Test.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter2Test.java new file mode 100644 index 00000000000..e5b4f86f0a1 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter2Test.java @@ -0,0 +1,1039 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.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 testUnescape2() { + String input = "digraph G {" + NL + + " a [ label=\" /\\\\n \"];" + NL + + " b [ label=\"\\\"Test\\\"\"];" + NL + + " a -> b; " + NL + + "}"; + 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("a", attrs.get("0").get("ID").getValue()); + assertEquals(" /\\\\n ", attrs.get("0").get("label").getValue()); + assertEquals("b", attrs.get("1").get("ID").getValue()); + assertEquals("\"Test\"", attrs.get("1").get("label").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")); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTUtilsTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTUtilsTest.java new file mode 100644 index 00000000000..ea1585caf48 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTUtilsTest.java @@ -0,0 +1,51 @@ +/* + * (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.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.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); + } +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/GEXFExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/GEXFExporterTest.java new file mode 100644 index 00000000000..ed0b4826131 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/GEXFExporterTest.java @@ -0,0 +1,250 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; +import org.xmlunit.builder.*; +import org.xmlunit.diff.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class GEXFExporterTest +{ + private static final String NL = System.lineSeparator(); + + // ~ 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 e12 = 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 == e12) { + 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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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 e12 = graph.addEdge("v1", "v2"); + graph.addVertex("v3"); + DefaultEdge e31 = graph.addEdge("v3", "v1"); + graph.setEdgeWeight(e31, 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 == e12) { + map.put("label", DefaultAttribute.createAttribute("Edge from node 1 to node 2")); + map.put("length", DefaultAttribute.createAttribute("100.0")); + } + if (e == e31) { + 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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporterTest.java new file mode 100644 index 00000000000..36146704bef --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFEventDrivenImporterTest.java @@ -0,0 +1,131 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class SimpleGEXFEventDrivenImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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(edge[0], e.getFirst()); + assertEquals(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()); + + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFImporterTest.java new file mode 100644 index 00000000000..22bcb51dc86 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFImporterTest.java @@ -0,0 +1,456 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class SimpleGEXFImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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 + public void testValidate() + { + assertThrows(ImportException.class, () -> { + // @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")); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlExporterTest.java new file mode 100644 index 00000000000..51c6db943ac --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlExporterTest.java @@ -0,0 +1,662 @@ +/* + * (C) Copyright 2006-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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.lineSeparator(); + + // @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_GRAPHICS_SECTION = + "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\tgraphics" + NL + + "\t\t[" + NL + + "\t\t\tfill \"#FF0000\"" + NL + + "\t\t]" + NL + + "\t]" + NL + + "\tnode" + NL + + "\t[" + NL + + "\t\tid 2" + NL + + "\t\tgraphics" + NL + + "\t\t[" + NL + + "\t\t\tfill \"#FF0000\"" + NL + + "\t\t]" + NL + + "\t]" + NL + + "\tedge" + NL + + "\t[" + NL + + "\t\tid 1" + NL + + "\t\tsource 1" + NL + + "\t\ttarget 2" + 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 testGraphicsSection() + throws UnsupportedEncodingException, + ExportException + { + Graph g = new SimpleGraph(DefaultEdge.class); + g.addVertex(V1); + g.addVertex(V2); + g.addEdge(V1, V2); + + GmlExporter exporter = new GmlExporter(); + exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_VERTEX_GRAPHICS_ATTRIBUTES, true); + exporter.setEdgeIdProvider(new IntegerIdProvider<>()); + exporter.setVertexGraphicsAttributeProvider(v -> Map.of("fill", DefaultAttribute.createAttribute("#FF0000"))); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + exporter.exportGraph(g, os); + String res = new String(os.toByteArray(), UTF_8); + assertEquals(UNDIRECTED_GRAPHICS_SECTION, 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 + public void testBadCustomVertexAttributes() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testBadCustomEdgeAttributeId() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testBadCustomEdgeAttributeSource() + { + assertThrows(IllegalArgumentException.class, () -> { + 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 + public void testBadCustomEdgeAttributeTarget() + { + assertThrows(IllegalArgumentException.class, () -> { + 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); + }); + } +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlImporterTest.java new file mode 100644 index 00000000000..32841879a60 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlImporterTest.java @@ -0,0 +1,952 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testNodeIdBadType() + throws ImportException + { + assertThrows(ImportException.class, () -> { + // @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 + public void testEdgeSourceBadType() + throws ImportException + { + assertThrows(ImportException.class, () -> { + // @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 + public void testEdgeTargetBadType() + { + assertThrows(ImportException.class, () -> { + // @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 + public void testEdgeWeightBadType() + { + assertThrows(ImportException.class, () -> { + // @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; + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ExporterTest.java new file mode 100644 index 00000000000..d35240bcdaa --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ExporterTest.java @@ -0,0 +1,241 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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(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); + } +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ImporterTest.java new file mode 100644 index 00000000000..208b57c5794 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ImporterTest.java @@ -0,0 +1,368 @@ +/* + * (C) Copyright 2017-2023, 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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.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<>(); + importer.importGraph(g, new InputStreamReader(in, UTF_8)); + return g; + } + + @Test + public void testExampleGraph() + throws ImportException + { + String input = ":Fa@x^\n"; + + Graph graph = readGraph( + new ByteArrayInputStream(input.getBytes(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(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)); + importer.importGraph( + graph, new InputStreamReader( + new ByteArrayInputStream(input.getBytes(UTF_8)), UTF_8)); + + 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(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(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(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(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(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(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(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(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(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(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(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(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(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); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLExporterTest.java new file mode 100644 index 00000000000..3d39b0d779c --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLExporterTest.java @@ -0,0 +1,657 @@ +/* + * (C) Copyright 2006-2023, 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.jupiter.api.*; +import org.xmlunit.builder.*; +import org.xmlunit.diff.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.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.lineSeparator(); + + // ~ 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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + + @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(diff.hasDifferences(), "XML identical " + diff.toString()); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLImporterTest.java new file mode 100644 index 00000000000..a311b6f4e06 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLImporterTest.java @@ -0,0 +1,1173 @@ +/* + * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class GraphMLImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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(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(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 + public void testNonValidNoVertexId() + { + assertThrows(ImportException.class, () -> { + // @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; + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporterTest.java new file mode 100644 index 00000000000..328fd96c771 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEdgeListImporterTest.java @@ -0,0 +1,146 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class SimpleGraphMLEdgeListImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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))); + + int[][] edges = { { 0, 2, 2 }, { 0, 1, 3 }, { 1, 2, 1 } }; + + assertEquals(3, collected.size()); + + int i = 0; + for (int[] edge : edges) { + Triple e = collected.get(i); + assertEquals(edge[0], e.getFirst()); + assertEquals(edge[1], e.getSecond()); + if (i < 2) { + assertEquals(edge[2], collected.get(i).getThird()); + } else { + assertNull(e.getThird()); + + } + i++; + } + + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporterTest.java new file mode 100644 index 00000000000..7c0563b8e4d --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLEventDrivenImporterTest.java @@ -0,0 +1,145 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class SimpleGraphMLEventDrivenImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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(edge[0], e.getFirst()); + assertEquals(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()); + + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLImporterTest.java new file mode 100644 index 00000000000..52e760588fb --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/SimpleGraphMLImporterTest.java @@ -0,0 +1,411 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests + * + * @author Dimitrios Michail + */ +public class SimpleGraphMLImporterTest +{ + + private static final String NL = System.lineSeparator(); + + @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 + public void testValidate() + { + assertThrows(ImportException.class, () -> { + // @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 + public void testNestedGraphs() + { + assertThrows(ImportException.class, () -> { + // @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"))); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONExporterTest.java new file mode 100644 index 00000000000..3b898236529 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONExporterTest.java @@ -0,0 +1,378 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; +import java.util.*; +import java.util.function.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testNotAllowedNanDouble() + { + assertThrows(IllegalArgumentException.class, () -> { + 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()); + + } + + @Test + public void testWithOtherNamesForVerticesAndEdges() + throws UnsupportedEncodingException + { + String expected = + "{\"creator\":\"JGraphT JSON Exporter\",\"version\":\"1\",\"vertices\":[{\"id\":\"1\"},{\"id\":\"2\"},{\"id\":\"3\"},{\"id\":\"4\"}],\"links\":[{\"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.setVerticesCollectionName("vertices"); + exporter.setEdgesCollectionName("links"); + exporter.setEdgeIdProvider(new IntegerIdProvider<>(1)); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + exporter.exportGraph(graph, os); + String res = new String(os.toByteArray(), UTF_8); + assertEquals(expected, res); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONImporterTest.java new file mode 100644 index 00000000000..8d3e13cc4ea --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONImporterTest.java @@ -0,0 +1,681 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.Assertions.*; + +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.jupiter.api.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 + public void testDuplicateNodeIds() + { + assertThrows(ImportException.class, () -> { + // @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 + public void testMissingSourceOnEdge() + { + assertThrows(ImportException.class, () -> { + // @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 + public void testMissingTargetOnEdge() + { + assertThrows(ImportException.class, () -> { + // @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")); + } + + @Test + public void testWithOtherNameForVerticesAndEdgesCollection() + throws ImportException + { + // @formatter:off + String input = "{\n" + + " \"vertices\": [\n" + + " { \"id\":\"1\" },\n" + + " { \"id\":\"2\" },\n" + + " { \"id\":\"3\" },\n" + + " { \"id\":\"4\" }\n" + + " ],\n" + + " \"rels\": [\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.setVerticesCollectionName("vertices"); + importer.setEdgesCollectionName("rels"); + 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")); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/lemon/LemonExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/lemon/LemonExporterTest.java new file mode 100644 index 00000000000..59785c49d2d --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/lemon/LemonExporterTest.java @@ -0,0 +1,203 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.io.*; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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.lineSeparator(); + + // @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)); + } + +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/matrix/MatrixExporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/matrix/MatrixExporterTest.java new file mode 100644 index 00000000000..08b5eeb89b5 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/matrix/MatrixExporterTest.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2003-2023, 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.jupiter.api.*; + +import java.io.*; + +import static org.junit.jupiter.api.Assertions.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.lineSeparator(); + + 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()); + } +} diff --git a/jgrapht-io/src/test/java/org/jgrapht/nio/tsplib/TSPLIBImporterTest.java b/jgrapht-io/src/test/java/org/jgrapht/nio/tsplib/TSPLIBImporterTest.java new file mode 100644 index 00000000000..47e10483048 --- /dev/null +++ b/jgrapht-io/src/test/java/org/jgrapht/nio/tsplib/TSPLIBImporterTest.java @@ -0,0 +1,780 @@ +/* + * (C) Copyright 2020-2023, 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.*; +import org.jgrapht.nio.tsplib.TSPLIBImporter.Node; +import org.junit.jupiter.api.*; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +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"); // also use other white-space than just a single space + fileContent.add("4\t10.8\t\t20.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(4, spec.getDimension()); + assertEquals(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 testMetaDataValues_slightyOfSpecMetadata() + { + // The metadata/specification section of the following file is slightly off the standard + // containing changes that were observed in the wild. + StringJoiner fileContent = new StringJoiner(System.lineSeparator()); + fileContent.add("NAME : theNameOfThisFile"); + fileContent.add("TYPE : TSP (Some comment)"); + fileContent.add("DIMENSION : 4 (Comment: number of elements)"); + fileContent.add("EDGE_WEIGHT_TYPE : EUC_2D #use 2D edges"); + fileContent.add("NODE_COORD_TYPE: THREED_COORDS"); + fileContent.add("CAPACITY : 7 # Capacitated vehicle routing problem data"); + 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\t15.0"); + fileContent.add("EOF"); + + Metadata metaData = + importGraphFromFile(fileContent).getSecond(); + + Specification spec = metaData.getSpecification(); + assertEquals("theNameOfThisFile", spec.getName()); + assertEquals("TSP", spec.getType()); + assertEquals(4, spec.getDimension()); + assertEquals(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(expectedEdgeSet.size(), graph.edgeSet().size(), "Unequal 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); + + assertNotNull(actualEdge); + 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()); + assertArrayEquals(expectedVector.getElementValues(), actualNode.getCoordinates()); + } + } + + 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()); + } + } +} diff --git a/jgrapht-io/src/test/resources/ellinghamHorton78Graph.s6 b/jgrapht-io/src/test/resources/ellinghamHorton78Graph.s6 new file mode 100644 index 00000000000..d9248560e07 --- /dev/null +++ b/jgrapht-io/src/test/resources/ellinghamHorton78Graph.s6 @@ -0,0 +1 @@ +>>sparse6<<:~?@M__EC?GEA_wQD`g]DAGOH`oiEAwqLbg}?CGCP_`IBCxCSc@URDhGV_ocXaG?IEgkZfXuWgiA^GQMaHIEhHA]eII[igAabIYaoJAuqJi}pizIrlJUrLjGvlRasMZiznJumNi{~kSAoOZ|AncN@PK@DkRXEls]wQCmnMSf~~~~~ \ No newline at end of file diff --git a/jgrapht-io/src/test/resources/myciel3.col b/jgrapht-io/src/test/resources/myciel3.col new file mode 100644 index 00000000000..8b806dafd25 --- /dev/null +++ b/jgrapht-io/src/test/resources/myciel3.col @@ -0,0 +1,26 @@ +c 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 diff --git a/jgrapht-io/src/test/resources/myciel3_weighted.col b/jgrapht-io/src/test/resources/myciel3_weighted.col new file mode 100644 index 00000000000..b9252fb7a45 --- /dev/null +++ b/jgrapht-io/src/test/resources/myciel3_weighted.col @@ -0,0 +1,26 @@ +c 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 diff --git a/jgrapht-opt/pom.xml b/jgrapht-opt/pom.xml new file mode 100644 index 00000000000..459c736ce83 --- /dev/null +++ b/jgrapht-opt/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/jgrapht-opt/src/main/java/module-info.java b/jgrapht-opt/src/main/java/module-info.java new file mode 100644 index 00000000000..e5dfa0b1f4d --- /dev/null +++ b/jgrapht-opt/src/main/java/module-info.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides specialized graph implementations. + * + * @since 1.5.0 + */ +module 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; +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupGSS.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupGSS.java new file mode 100644 index 00000000000..3f9281e4e32 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupGSS.java @@ -0,0 +1,78 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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<>()); + } + }; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupIntVertexGSS.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupIntVertexGSS.java new file mode 100644 index 00000000000..49106ead14b --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilFastLookupIntVertexGSS.java @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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<>()); + } + }; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilGSS.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilGSS.java new file mode 100644 index 00000000000..199444962c6 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilGSS.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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<>()); + } + }; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilIntVertexGSS.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilIntVertexGSS.java new file mode 100644 index 00000000000..ed391251e10 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilIntVertexGSS.java @@ -0,0 +1,72 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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<>()); + } + }; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapGraph.java new file mode 100644 index 00000000000..6e7d3def965 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapGraph.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapIntVertexGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapIntVertexGraph.java new file mode 100644 index 00000000000..d93ef14b515 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapIntVertexGraph.java @@ -0,0 +1,86 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/package-info.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/package-info.java new file mode 100644 index 00000000000..2c11c9f2ebb --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2018-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Specialized graph implementations using the FastUtil library + */ +package org.jgrapht.opt.graph.fastutil; diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/IncomingEdgesSupport.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/IncomingEdgesSupport.java new file mode 100644 index 00000000000..4296cc9986b --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/IncomingEdgesSupport.java @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2021-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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, +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedGraph.java new file mode 100644 index 00000000000..2011900be2c --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedGraph.java @@ -0,0 +1,119 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + }); + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedWeightedGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedWeightedGraph.java new file mode 100644 index 00000000000..a8563fc6db1 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntDirectedWeightedGraph.java @@ -0,0 +1,152 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedGraph.java new file mode 100644 index 00000000000..cb71accf9c8 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedGraph.java @@ -0,0 +1,87 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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)); + } +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedWeightedGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedWeightedGraph.java new file mode 100644 index 00000000000..22ed9e2227e --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/SparseIntUndirectedWeightedGraph.java @@ -0,0 +1,132 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/package-info.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/package-info.java new file mode 100644 index 00000000000..ade7f3db7bd --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2019-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Specialized graph implementations using sparse matrix representations. + */ +package org.jgrapht.opt.graph.sparse; diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/AbstractSparseSpecificsGraph.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/AbstractSparseSpecificsGraph.java new file mode 100644 index 00000000000..5d6ab73bf2c --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/AbstractSparseSpecificsGraph.java @@ -0,0 +1,207 @@ +/* + * (C) Copyright 2021-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CSRBooleanMatrix.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CSRBooleanMatrix.java new file mode 100644 index 00000000000..0fe5af4ce4d --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CSRBooleanMatrix.java @@ -0,0 +1,194 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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++]; + } + + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CompleteIntegerSet.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CompleteIntegerSet.java new file mode 100644 index 00000000000..ca5e846dd0e --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/CompleteIntegerSet.java @@ -0,0 +1,66 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncidenceMatrixSparseUndirectedSpecifics.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncidenceMatrixSparseUndirectedSpecifics.java new file mode 100644 index 00000000000..f9a30e8ab80 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncidenceMatrixSparseUndirectedSpecifics.java @@ -0,0 +1,213 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncomingNoReindexSparseDirectedSpecifics.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncomingNoReindexSparseDirectedSpecifics.java new file mode 100644 index 00000000000..ffde31d9011 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/IncomingNoReindexSparseDirectedSpecifics.java @@ -0,0 +1,121 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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); + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/NoIncomingNoReindexSparseDirectedSpecifics.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/NoIncomingNoReindexSparseDirectedSpecifics.java new file mode 100644 index 00000000000..3d3dfda6932 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/NoIncomingNoReindexSparseDirectedSpecifics.java @@ -0,0 +1,213 @@ +/* + * (C) Copyright 2020-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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; + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/SparseGraphSpecifics.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/SparseGraphSpecifics.java new file mode 100644 index 00000000000..ea703a7c823 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/SparseGraphSpecifics.java @@ -0,0 +1,345 @@ +/* + * (C) Copyright 2021-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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 {@code true} if this graph contains the specified edge. More formally, returns + * {@code true} if and only if this graph contains an edge {@code e2} such that + * {@code e.equals(e2)}. If the specified edge is {@code null} returns + * {@code false}. + * + * @param e edge whose presence in this graph is to be tested. + * + * @return {@code true} if this graph contains the specified edge. + */ + default boolean containsEdge(Integer e) + { + return e >= 0 && e < edgesCount(); + } + + /** + * Returns {@code true} if this graph contains the specified vertex. More formally, returns + * {@code true} if and only if this graph contains a vertex {@code u} such that + * {@code u.equals(v)}. If the specified vertex is {@code null} returns + * {@code false}. + * + * @param v vertex whose presence in this graph is to be tested. + * + * @return {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code 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 {@code null}. If any of the specified vertices is + * {@code null} returns {@code 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 {@code null}, returns + * {@code 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 {@code 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 {@code 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()); + } + } + +} diff --git a/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/package-info.java b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/package-info.java new file mode 100644 index 00000000000..21b517a5864 --- /dev/null +++ b/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/package-info.java @@ -0,0 +1,22 @@ +/* + * (C) Copyright 2021-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Implementations of different sparse graphs with different tradeoffs. + */ +package org.jgrapht.opt.graph.sparse.specifics; diff --git a/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapGraphTest.java b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapGraphTest.java new file mode 100644 index 00000000000..b0136637e84 --- /dev/null +++ b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapGraphTest.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +/** + * 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())); + } + +} diff --git a/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapIntVertexGraphTest.java b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapIntVertexGraphTest.java new file mode 100644 index 00000000000..cf39230fead --- /dev/null +++ b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/FastUtilMapIntVertexGraphTest.java @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2018-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +/** + * 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())); + } + +} diff --git a/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/IncomingOutgoingEdgesTest.java b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/IncomingOutgoingEdgesTest.java new file mode 100644 index 00000000000..edbffb16c2d --- /dev/null +++ b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/IncomingOutgoingEdgesTest.java @@ -0,0 +1,296 @@ +/* + * (C) Copyright 2017-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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()); + } + +} diff --git a/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/sparse/SparseIntGraphTest.java b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/sparse/SparseIntGraphTest.java new file mode 100644 index 00000000000..f4f7f4ce567 --- /dev/null +++ b/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/sparse/SparseIntGraphTest.java @@ -0,0 +1,814 @@ +/* + * (C) Copyright 2019-2023, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.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.jupiter.api.*; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 + public void testDirectedLazyNoIncomingFail() + { + assertThrows(UnsupportedOperationException.class, () -> 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, 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(0, g.getEdgeSource(0)); + assertEquals(5, g.getEdgeTarget(0)); + assertEquals(0, g.getEdgeSource(1)); + assertEquals(2, g.getEdgeTarget(1)); + assertEquals(3, g.getEdgeSource(2)); + assertEquals(4, g.getEdgeTarget(2)); + assertEquals(1, g.getEdgeSource(3)); + assertEquals(4, g.getEdgeTarget(3)); + assertEquals(0, g.getEdgeSource(4)); + assertEquals(1, g.getEdgeTarget(4)); + assertEquals(3, g.getEdgeSource(5)); + assertEquals(1, g.getEdgeTarget(5)); + assertEquals(2, g.getEdgeSource(6)); + assertEquals(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(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(0, g.getEdgeSource(0)); + assertEquals(0, g.getEdgeTarget(0)); + assertEquals(0, g.getEdgeSource(1)); + assertEquals(1, g.getEdgeTarget(1)); + assertEquals(0, g.getEdgeSource(2)); + assertEquals(2, g.getEdgeTarget(2)); + assertEquals(0, g.getEdgeSource(3)); + assertEquals(0, g.getEdgeTarget(3)); + assertEquals(0, g.getEdgeSource(4)); + assertEquals(1, g.getEdgeTarget(4)); + assertEquals(1, g.getEdgeSource(5)); + assertEquals(1, g.getEdgeTarget(5)); + assertEquals(1, g.getEdgeSource(6)); + assertEquals(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(0, g.getEdgeSource(0)); + assertEquals(5, g.getEdgeTarget(0)); + assertEquals(1d, g.getEdgeWeight(0), 1e-16); + assertEquals(0, g.getEdgeSource(1)); + assertEquals(2, g.getEdgeTarget(1)); + assertEquals(2d, g.getEdgeWeight(1), 1e-16); + assertEquals(3, g.getEdgeSource(2)); + assertEquals(4, g.getEdgeTarget(2)); + assertEquals(3d, g.getEdgeWeight(2), 1e-16); + assertEquals(1, g.getEdgeSource(3)); + assertEquals(4, g.getEdgeTarget(3)); + assertEquals(4d, g.getEdgeWeight(3), 1e-16); + assertEquals(0, g.getEdgeSource(4)); + assertEquals(1, g.getEdgeTarget(4)); + assertEquals(5d, g.getEdgeWeight(4), 1e-16); + assertEquals(3, g.getEdgeSource(5)); + assertEquals(1, g.getEdgeTarget(5)); + assertEquals(6d, g.getEdgeWeight(5), 1e-16); + assertEquals(2, g.getEdgeSource(6)); + assertEquals(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(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, 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(0, g.getEdgeSource(0)); + assertEquals(1, g.getEdgeTarget(0)); + assertEquals(1, g.getEdgeSource(1)); + assertEquals(0, g.getEdgeTarget(1)); + assertEquals(1, g.getEdgeSource(2)); + assertEquals(4, g.getEdgeTarget(2)); + assertEquals(1, g.getEdgeSource(3)); + assertEquals(5, g.getEdgeTarget(3)); + assertEquals(1, g.getEdgeSource(4)); + assertEquals(6, g.getEdgeTarget(4)); + assertEquals(2, g.getEdgeSource(5)); + assertEquals(4, g.getEdgeTarget(5)); + assertEquals(2, g.getEdgeSource(6)); + assertEquals(4, g.getEdgeTarget(6)); + assertEquals(2, g.getEdgeSource(7)); + assertEquals(4, g.getEdgeTarget(7)); + assertEquals(3, g.getEdgeSource(8)); + assertEquals(4, g.getEdgeTarget(8)); + assertEquals(4, g.getEdgeSource(9)); + assertEquals(5, g.getEdgeTarget(9)); + assertEquals(5, g.getEdgeSource(10)); + assertEquals(6, g.getEdgeTarget(10)); + assertEquals(7, g.getEdgeSource(11)); + assertEquals(6, g.getEdgeTarget(11)); + assertEquals(7, g.getEdgeSource(12)); + assertEquals(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(0, g.getEdge(0, 1)); + assertEquals(Set.of(0), g.getAllEdges(0, 1)); + assertEquals(1, g.getEdge(1, 0)); + assertEquals(Set.of(1), g.getAllEdges(1, 0)); + assertEquals(2, g.getEdge(1, 4)); + assertEquals(Set.of(2), g.getAllEdges(1, 4)); + assertEquals(3, g.getEdge(1, 5)); + assertEquals(Set.of(3), g.getAllEdges(1, 5)); + assertEquals(4, g.getEdge(1, 6)); + assertEquals(Set.of(4), g.getAllEdges(1, 6)); + assertEquals(5, g.getEdge(2, 4)); + assertEquals(Set.of(5, 6, 7), g.getAllEdges(2, 4)); + assertEquals(8, g.getEdge(3, 4)); + assertEquals(Set.of(8), g.getAllEdges(3, 4)); + assertEquals(9, g.getEdge(4, 5)); + assertEquals(Set.of(9), g.getAllEdges(4, 5)); + assertEquals(10, g.getEdge(5, 6)); + assertEquals(Set.of(10), g.getAllEdges(5, 6)); + assertEquals(11, g.getEdge(7, 6)); + assertEquals(Set.of(11), g.getAllEdges(7, 6)); + assertEquals(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, 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(0, g.getEdgeSource(0)); + assertEquals(1, g.getEdgeTarget(0)); + assertEquals(1, g.getEdgeSource(1)); + assertEquals(0, g.getEdgeTarget(1)); + assertEquals(1, g.getEdgeSource(2)); + assertEquals(4, g.getEdgeTarget(2)); + assertEquals(1, g.getEdgeSource(3)); + assertEquals(5, g.getEdgeTarget(3)); + assertEquals(1, g.getEdgeSource(4)); + assertEquals(6, g.getEdgeTarget(4)); + assertEquals(2, g.getEdgeSource(5)); + assertEquals(4, g.getEdgeTarget(5)); + assertEquals(2, g.getEdgeSource(6)); + assertEquals(4, g.getEdgeTarget(6)); + assertEquals(2, g.getEdgeSource(7)); + assertEquals(4, g.getEdgeTarget(7)); + assertEquals(3, g.getEdgeSource(8)); + assertEquals(4, g.getEdgeTarget(8)); + assertEquals(4, g.getEdgeSource(9)); + assertEquals(5, g.getEdgeTarget(9)); + assertEquals(5, g.getEdgeSource(10)); + assertEquals(6, g.getEdgeTarget(10)); + assertEquals(7, g.getEdgeSource(11)); + assertEquals(6, g.getEdgeTarget(11)); + assertEquals(7, g.getEdgeSource(12)); + assertEquals(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(0, g.getEdge(0, 1)); + assertEquals(Collections.singleton(Integer.valueOf(0)), g.getAllEdges(0, 1)); + assertEquals(1, g.getEdge(1, 0)); + assertEquals(Collections.singleton(Integer.valueOf(1)), g.getAllEdges(1, 0)); + assertEquals(2, g.getEdge(1, 4)); + assertEquals(Collections.singleton(Integer.valueOf(2)), g.getAllEdges(1, 4)); + assertEquals(3, g.getEdge(1, 5)); + assertEquals(Collections.singleton(Integer.valueOf(3)), g.getAllEdges(1, 5)); + assertEquals(4, g.getEdge(1, 6)); + assertEquals(Collections.singleton(Integer.valueOf(4)), g.getAllEdges(1, 6)); + assertEquals(5, g.getEdge(2, 4)); + assertEquals(new HashSet<>(Arrays.asList(5, 6, 7)), g.getAllEdges(2, 4)); + assertEquals(8, g.getEdge(3, 4)); + assertEquals(Collections.singleton(Integer.valueOf(8)), g.getAllEdges(3, 4)); + assertEquals(9, g.getEdge(4, 5)); + assertEquals(Collections.singleton(Integer.valueOf(9)), g.getAllEdges(4, 5)); + assertEquals(10, g.getEdge(5, 6)); + assertEquals(Collections.singleton(Integer.valueOf(10)), g.getAllEdges(5, 6)); + assertEquals(11, g.getEdge(7, 6)); + assertEquals(Collections.singleton(Integer.valueOf(11)), g.getAllEdges(7, 6)); + assertEquals(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(0, g.getEdgeSource(0)); + assertEquals(1, g.getEdgeTarget(0)); + assertEquals(0d, g.getEdgeWeight(0), 1e-16); + assertEquals(1, g.getEdgeSource(1)); + assertEquals(0, g.getEdgeTarget(1)); + assertEquals(1d, g.getEdgeWeight(1), 1e-16); + assertEquals(1, g.getEdgeSource(2)); + assertEquals(4, g.getEdgeTarget(2)); + assertEquals(2d, g.getEdgeWeight(2), 1e-16); + assertEquals(1, g.getEdgeSource(3)); + assertEquals(5, g.getEdgeTarget(3)); + assertEquals(3d, g.getEdgeWeight(3), 1e-16); + assertEquals(1, g.getEdgeSource(4)); + assertEquals(6, g.getEdgeTarget(4)); + assertEquals(4d, g.getEdgeWeight(4), 1e-16); + assertEquals(2, g.getEdgeSource(5)); + assertEquals(4, g.getEdgeTarget(5)); + assertEquals(5d, g.getEdgeWeight(5), 1e-16); + assertEquals(2, g.getEdgeSource(6)); + assertEquals(4, g.getEdgeTarget(6)); + assertEquals(6d, g.getEdgeWeight(6), 1e-16); + assertEquals(2, g.getEdgeSource(7)); + assertEquals(4, g.getEdgeTarget(7)); + assertEquals(7d, g.getEdgeWeight(7), 1e-16); + assertEquals(3, g.getEdgeSource(8)); + assertEquals(4, g.getEdgeTarget(8)); + assertEquals(8d, g.getEdgeWeight(8), 1e-16); + assertEquals(4, g.getEdgeSource(9)); + assertEquals(5, g.getEdgeTarget(9)); + assertEquals(9d, g.getEdgeWeight(9), 1e-16); + assertEquals(5, g.getEdgeSource(10)); + assertEquals(6, g.getEdgeTarget(10)); + assertEquals(10d, g.getEdgeWeight(10), 1e-16); + assertEquals(7, g.getEdgeSource(11)); + assertEquals(6, g.getEdgeTarget(11)); + assertEquals(11d, g.getEdgeWeight(11), 1e-16); + assertEquals(7, g.getEdgeSource(12)); + assertEquals(7, g.getEdgeTarget(12)); + assertEquals(12d, g.getEdgeWeight(12), 1e-16); + + for (int i = 0; i < edges.size(); i++) { + assertEquals(i, g.getEdgeWeight(i), 1e-16); + g.setEdgeWeight(i, 100 + g.getEdgeWeight(i)); + assertEquals(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(0, g.getEdge(0, 1)); + assertEquals(Set.of(0), g.getAllEdges(0, 1)); + assertEquals(1, g.getEdge(1, 0)); + assertEquals(Set.of(1), g.getAllEdges(1, 0)); + assertEquals(2, g.getEdge(1, 4)); + assertEquals(Set.of(2), g.getAllEdges(1, 4)); + assertEquals(3, g.getEdge(1, 5)); + assertEquals(Set.of(3), g.getAllEdges(1, 5)); + assertEquals(4, g.getEdge(1, 6)); + assertEquals(Set.of(4), g.getAllEdges(1, 6)); + assertEquals(5, g.getEdge(2, 4)); + assertEquals(Set.of(5, 6, 7), g.getAllEdges(2, 4)); + assertEquals(8, g.getEdge(3, 4)); + assertEquals(Set.of(8), g.getAllEdges(3, 4)); + assertEquals(9, g.getEdge(4, 5)); + assertEquals(Set.of(9), g.getAllEdges(4, 5)); + assertEquals(10, g.getEdge(5, 6)); + assertEquals(Set.of(10), g.getAllEdges(5, 6)); + assertEquals(11, g.getEdge(7, 6)); + assertEquals(Set.of(11), g.getAllEdges(7, 6)); + assertEquals(12, g.getEdge(7, 7)); + assertEquals(Set.of(12), g.getAllEdges(7, 7)); + + } + +} diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar deleted file mode 100644 index 0131e899961..00000000000 Binary files a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar and /dev/null differ diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.md5 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.md5 deleted file mode 100644 index c2ce8b79b46..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -cad8db6899c1d7e3eb11785a6a67920d \ No newline at end of file diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.sha1 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.sha1 deleted file mode 100644 index 8f38571f5c0..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -833ef9304849e12ec0825784d2ff29f891d7501a \ No newline at end of file diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom deleted file mode 100644 index 0231070c1a7..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom +++ /dev/null @@ -1,8 +0,0 @@ - - - 4.0.0 - com.touchgraph - TGGraphLayout - UNKNOWN - diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.md5 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.md5 deleted file mode 100644 index 746b8a5b235..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -c6180b7be7913c5245c805edad10e4a4 \ No newline at end of file diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.sha1 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.sha1 deleted file mode 100644 index afeedd8667d..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/UNKNOWN/TGGraphLayout-UNKNOWN.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -9b145ab66de6495f465cf72027a53d6cd1b48e03 \ No newline at end of file diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml deleted file mode 100644 index fa505c8b61f..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - com.touchgraph - TGGraphLayout - - UNKNOWN - - UNKNOWN - - 20120302174447 - - diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.md5 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.md5 deleted file mode 100644 index 9b45b865ddb..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.md5 +++ /dev/null @@ -1 +0,0 @@ -a89e47daa93b178222cd9ee1f1dceba2 \ No newline at end of file diff --git a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.sha1 b/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.sha1 deleted file mode 100644 index 7c62c7563b5..00000000000 --- a/jgrapht-touchgraph/3rdparty/com/touchgraph/TGGraphLayout/maven-metadata.xml.sha1 +++ /dev/null @@ -1 +0,0 @@ -22e09ddb87ce996a95c10db4ca616d3f5993513a \ No newline at end of file diff --git a/jgrapht-touchgraph/TG-APACHE-LICENSE.txt b/jgrapht-touchgraph/TG-APACHE-LICENSE.txt deleted file mode 100644 index 2373f7298f9..00000000000 --- a/jgrapht-touchgraph/TG-APACHE-LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * TouchGraph LLC. Apache-Style Software License - * - * - * Copyright (c) 2001-2002 Alexander Shapiro. 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. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: - * "This product includes software developed by - * TouchGraph LLC (http://www.touchgraph.com/)." - * Alternately, this acknowledgment may appear in the software itself, - * if and wherever such third-party acknowledgments normally appear. - * - * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse - * or promote products derived from this software without prior written - * permission. For written permission, please contact - * alex@touchgraph.com - * - * 5. Products derived from this software may not be called "TouchGraph", - * nor may "TouchGraph" appear in their name, without prior written - * permission of alex@touchgraph.com. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 TOUCHGRAPH OR ITS CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - */ diff --git a/jgrapht-touchgraph/pom.xml b/jgrapht-touchgraph/pom.xml deleted file mode 100644 index 948257f4e91..00000000000 --- a/jgrapht-touchgraph/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - net.sf.jgrapht - jgrapht - 0.8.3-SNAPSHOT - - jgrapht-touchgraph - JGraphT - Touchgraph - - - - org.apache.maven.plugins - maven-shade-plugin - 1.5 - - - package - - shade - - - - - ${project.artifactId}-${project.version}-shade - - - - - - - ${project.groupId} - jgrapht-ext - - - com.touchgraph - TGGraphLayout - UNKNOWN - - - - - - 3rd-party-dependencies - file:${basedir}/3rdparty - - - diff --git a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/SimpleTouchgraphApplet.java b/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/SimpleTouchgraphApplet.java deleted file mode 100644 index 5a35b0b7e6c..00000000000 --- a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/SimpleTouchgraphApplet.java +++ /dev/null @@ -1,132 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * SimpleTouchgraphApplet.java - * ------------------- - * (C) Copyright 2006-2008, by Carl Anderson and Contributors. - * - * Original Author: Carl Anderson - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 8-May-2006 : Initial revision (CA); - * - */ -package org.jgrapht.experimental.touchgraph; - -import java.applet.*; - -import java.awt.*; - -import javax.swing.*; - -import org.jgrapht.*; -import org.jgrapht.graph.*; - - -/** - * SimpleTouchgraphApplet - * - * @author canderson - */ -public class SimpleTouchgraphApplet - extends Applet -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = 6213379835360007840L; - - //~ Methods ---------------------------------------------------------------- - - /** - * create a graph: code taken from non-visible - * org._3pq.jgrapht.demo.createStringGraph() - */ - public static Graph createSamplegraph() - { - UndirectedGraph 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; - } - - /** - * initialize the applet - */ - public void init() - { - Graph g = createSamplegraph(); - boolean selfReferencesAllowed = false; - - setLayout(new BorderLayout()); - setSize(800, 600); - add( - new TouchgraphPanel(g, selfReferencesAllowed), - BorderLayout.CENTER); - } - - public static void main(String [] args) - { - Graph g = createSamplegraph(); - boolean selfReferencesAllowed = false; - - JFrame frame = new JFrame(); - frame.getContentPane().add( - new TouchgraphPanel(g, selfReferencesAllowed)); - frame.setPreferredSize(new Dimension(800, 800)); - frame.setTitle("JGraphT to Touchgraph Converter Demo"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.pack(); - frame.setVisible(true); - try { - Thread.sleep(5000000); - } catch (InterruptedException ex) { - } - } -} - -// End SimpleTouchgraphApplet.java diff --git a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphConverter.java b/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphConverter.java deleted file mode 100644 index fd789be6e6d..00000000000 --- a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphConverter.java +++ /dev/null @@ -1,134 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * TouchgraphConverter.java - * ------------------- - * (C) Copyright 2006-2008, by Carl Anderson and Contributors. - * - * Original Author: Carl Anderson - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 8-May-2006 : Initial revision (CA); - * - */ -package org.jgrapht.experimental.touchgraph; - -import com.touchgraph.graphlayout.*; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * A Converter class that converts a JGraphT graph to that used in the - * TouchGraph library. - * - * @author canderson - */ -public class TouchgraphConverter -{ - //~ Methods ---------------------------------------------------------------- - - /** - * Convert a JGraphT graph to the representation used in the TouchGraph - * library. http://sourceforge.net/projects/touchgraph TouchGraph doesn't - * have a sensible, extensible graph object class and so one has to add them - * to a TGPanel which will store the graph components (the set of nodes and - * edges) in its own way. The closest Touchgraph has to a graph object is a - * GraphEltSet but Touchgraph does not provide the visibility to use it - * easily and one can use a JGraphT graph. While JGraphT nodes can be any - * type of objects, TouchGraph uses a set of com.touchgraph.graphlayout.Node - * and com.touchgraph.graphlayout.Edge only. Moreover, TouchGraph edges are - * always directed. Having said that, if you want a nice way to visualize - * and explore a graph, especially large complex graphs, TouchGraph is very - * nice - * - * @param graph: the JGraphT graph - * @param tgPanel: the TouchGraph TGPanel - * @param selfReferencesAllowed: do you want to include self-referenctial - * edges, ie an edge from a node to itself? Self-referential loops do not - * show up in the TG visualization but you may want to subclass TG's Node - * class to show them - * - * @return first node of the TouchGraph graph - * - * @throws TGException - */ - public Node convertToTouchGraph( - Graph graph, - TGPanel tgPanel, - boolean selfReferencesAllowed) - throws TGException - { - List jgtNodes = new ArrayList(graph.vertexSet()); - Node [] tgNodes = new Node[jgtNodes.size()]; - - // add all the nodes... - for (int i = 0; i < jgtNodes.size(); i++) { - Node n; - if (jgtNodes.get(i) instanceof Node) { - // if our JGraphT object was a touchGraph node, add it unaltered - n = (Node) jgtNodes.get(i); - } else { - // create a TG Node with a "label" and "id" equals to the - // objects toString() value - n = new Node(jgtNodes.get(i).toString()); - } - - // store this for edge-related creation below - tgNodes[i] = n; - - // add the node to the TG panel - tgPanel.addNode(n); - } - - // add the edges... - for (int i = 0; i < tgNodes.length; i++) { - for (int j = 0; j < tgNodes.length; j++) { - // self-referential loops do not show up in the TG - // visualization but you may want to - // subclass TG's Node class to show them - if ((i != j) || selfReferencesAllowed) { - if (graph.getEdge(jgtNodes.get(i), jgtNodes.get(j)) - != null) - { - // add TG directed edge from i to j - tgPanel.addEdge(new Edge(tgNodes[i], tgNodes[j])); - } - } - } - } - - // return the first node as a focal point for the TG panel - return tgNodes[0]; - } -} - -// End TouchgraphConverter.java diff --git a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphPanel.java b/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphPanel.java deleted file mode 100644 index 0ec9f86b973..00000000000 --- a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/TouchgraphPanel.java +++ /dev/null @@ -1,166 +0,0 @@ -/* ========================================== - * JGraphT : a free Java graph-theory library - * ========================================== - * - * Project Info: http://jgrapht.sourceforge.net/ - * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) - * - * (C) Copyright 2003-2008, by Barak Naveh and Contributors. - * - * 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. - */ -/* ------------------- - * TouchgraphPanel.java - * ------------------- - * (C) Copyright 2006-2008, by Carl Anderson and Contributors. - * - * Original Author: Carl Anderson - * Contributor(s): - - * - * $Id$ - * - * Changes - * ------- - * 8-May-2006 : Initial revision (CA); - * - */ -package org.jgrapht.experimental.touchgraph; - -import com.touchgraph.graphlayout.*; -import com.touchgraph.graphlayout.interaction.*; - -import java.awt.*; - -import java.util.*; - -import org.jgrapht.*; - - -/** - * The Touchgraph panel that displays our graph - * http://sourceforge.net/projects/touchgraph - * - * @author canderson - */ -public class TouchgraphPanel - extends GLPanel -{ - //~ Static fields/initializers --------------------------------------------- - - /** - */ - private static final long serialVersionUID = -7441058429719746032L; - - //~ Instance fields -------------------------------------------------------- - - private Color defaultBackColor = new Color(0x01, 0x11, 0x44); - private Color defaultBorderBackColor = new Color(0x02, 0x35, 0x81); - private Color defaultForeColor = - new Color((float) 0.95, (float) 0.85, (float) 0.55); - - /** - * the JGraphT graph we are displaying - */ - Graph graph; - - /** - * are self-references allowed? They will not show up in TouchGraph unless - * you override Touchgraph's Node or Edge class to do so - */ - boolean selfReferencesAllowed = true; - - // ================= - - //~ Constructors ----------------------------------------------------------- - - /**constructor*/ - public TouchgraphPanel(Graph graph, boolean selfReferencesAllowed) - { - this.graph = graph; - this.selfReferencesAllowed = selfReferencesAllowed; - - /* - * The code that was in the super's constructor. As it also called - * super's initialize() - * it is impossible to subclass and insert our own graph into the - * initialization process - */ - preinitialize(); - - initialize(); // now we can insert our own graph into this method - } - - //~ Methods ---------------------------------------------------------------- - - /** - * get everything setup: this is the code that was in the super's - * constructor but which was followed by an initialize() call. Hence, it was - * impossible to subclass the superclass and insert our own graph - * initialization code without breaking it out as here. - */ - public void preinitialize() - { - this.setBackground(defaultBorderBackColor); - this.setForeground(defaultForeColor); - scrollBarHash = new Hashtable(); - tgLensSet = new TGLensSet(); - tgPanel = new TGPanel(); - tgPanel.setBackColor(defaultBackColor); - hvScroll = new HVScroll(tgPanel, tgLensSet); - zoomScroll = new ZoomScroll(tgPanel); - hyperScroll = new HyperScroll(tgPanel); - rotateScroll = new RotateScroll(tgPanel); - localityScroll = new LocalityScroll(tgPanel); - } - - /** - * Initialize panel, lens, and establish a random graph as a demonstration. - */ - public void initialize() - { - buildPanel(); - buildLens(); - tgPanel.setLensSet(tgLensSet); - addUIs(); - try { - if (this.graph == null) { - /* - * Add a random graph - */ - randomGraph(); - } else { - /* - * Add users graph - */ - TouchgraphConverter converter = - new TouchgraphConverter(); - Node n = - (Node) converter.convertToTouchGraph( - this.graph, - tgPanel, - this.selfReferencesAllowed); - getHVScroll().slowScrollToCenter(n); - tgPanel.setLocale(n, Integer.MAX_VALUE); - } - } catch (TGException tge) { - System.err.println(tge.getMessage()); - tge.printStackTrace(System.err); - } - setVisible(true); - } -} - -// End TouchgraphPanel.java diff --git a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/package.html b/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/package.html deleted file mode 100644 index 6a6bd2ac219..00000000000 --- a/jgrapht-touchgraph/src/main/java/org/jgrapht/experimental/touchgraph/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -

    Integration with the Touchgraph project.

    - - - diff --git a/jgrapht-unimi-dsi/pom.xml b/jgrapht-unimi-dsi/pom.xml new file mode 100644 index 00000000000..1202f31ac76 --- /dev/null +++ b/jgrapht-unimi-dsi/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + + org.jgrapht + jgrapht + 1.5.3-SNAPSHOT + + 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 + + + it.unimi.dsi + dsiutils + + + org.apache.commons + commons-configuration2 + + + org.apache.commons + commons-text + + + commons-logging + commons-logging + + + org.apache.commons + commons-lang3 + + + + + it.unimi.dsi + sux4j + + + 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 + + + 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 + + + net.sf.jung + * + + + org.apache.commons + commons-configuration2 + + + org.apache.commons + commons-text + + + commons-logging + commons-logging + + + org.apache.commons + commons-lang3 + + + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/jgrapht-unimi-dsi/src/main/java/module-info.java b/jgrapht-unimi-dsi/src/main/java/module-info.java new file mode 100644 index 00000000000..c50990326dc --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/module-info.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2020-2024, by Dimitrios Michail and Contributors. + * + * JGraphT : a free Java graph-theory library + * + * See the CONTRIBUTORS.md file distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the + * GNU Lesser General Public License v2.1 or later + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. + * + * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later + */ + +/** + * Provides graph implementations using succinct data structures. + * + * @since 1.5.1 + */ +module 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; +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctDirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctDirectedGraph.java new file mode 100644 index 00000000000..212be09606f --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctDirectedGraph.java @@ -0,0 +1,200 @@ +/* + * (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(); + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctGraph.java new file mode 100644 index 00000000000..0ca8c8ed71e --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctGraph.java @@ -0,0 +1,169 @@ +/* + * (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 {@code 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); + } + +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctUndirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctUndirectedGraph.java new file mode 100644 index 00000000000..cb964b21d17 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctUndirectedGraph.java @@ -0,0 +1,244 @@ +/* + * (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(); + } + +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctDirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctDirectedGraph.java new file mode 100644 index 00000000000..ee40f02621c --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctDirectedGraph.java @@ -0,0 +1,535 @@ +/* + * (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), incomingEdgesSupport); + } + + /** + * 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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntDirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntDirectedGraph.java new file mode 100644 index 00000000000..bd58636a06e --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntDirectedGraph.java @@ -0,0 +1,457 @@ +/* + * (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), + incomingEdgesSupport); + } + + /** + * 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 {@code 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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraph.java new file mode 100644 index 00000000000..4ce137792fa --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraph.java @@ -0,0 +1,350 @@ +/* + * (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 undirected 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 {@code 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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctUndirectedGraph.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctUndirectedGraph.java new file mode 100644 index 00000000000..82b9f801080 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctUndirectedGraph.java @@ -0,0 +1,424 @@ +/* + * (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 undirected 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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/package-info.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/package-info.java new file mode 100644 index 00000000000..ba580aaf412 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/package-info.java @@ -0,0 +1,116 @@ +/* + * (C) Copyright 2021-2024, 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 + */ + +/** + * 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 java.lang.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 java.lang.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(java.lang.CharSequence)} ImmutableGraph.loadMapped()}; + *
    4. use the copy constructor to obtain a quasi-succinct representation. + *
    + */ +package org.jgrapht.sux4j; diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableBigGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableBigGraphAdapter.java new file mode 100644 index 00000000000..0ff5a4f3205 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableBigGraphAdapter.java @@ -0,0 +1,205 @@ +/* + * (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(); + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableGraphAdapter.java new file mode 100644 index 00000000000..db2da3cd32a --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/AbstractImmutableGraphAdapter.java @@ -0,0 +1,206 @@ +/* + * (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(); + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapter.java new file mode 100644 index 00000000000..1fe80e3dc41 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapter.java @@ -0,0 +1,364 @@ +/* + * (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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapter.java new file mode 100644 index 00000000000..e068ab67995 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapter.java @@ -0,0 +1,422 @@ +/* + * (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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapter.java new file mode 100644 index 00000000000..e17bb342f68 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapter.java @@ -0,0 +1,343 @@ +/* + * (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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapter.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapter.java new file mode 100644 index 00000000000..212c0d628e9 --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapter.java @@ -0,0 +1,330 @@ +/* + * (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; + } +} diff --git a/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/package-info.java b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/package-info.java new file mode 100644 index 00000000000..7098e0b65cf --- /dev/null +++ b/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/package-info.java @@ -0,0 +1,24 @@ +/* + * (C) Copyright 2020-2024, 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 + */ + +/** + * Adapters for graphs stored using + * WebGraph's compressed and succinct + * formats. + */ +package org.jgrapht.webgraph; diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctDirectedGraphTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctDirectedGraphTest.java new file mode 100644 index 00000000000..528d50ff523 --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctDirectedGraphTest.java @@ -0,0 +1,279 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +import org.jgrapht.alg.util.Pair; +import org.jgrapht.generate.GnpRandomGraphGenerator; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.util.SupplierUtil; +import org.junit.jupiter.api.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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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 testOutgoingOnly() + { + final List> edges = + List.of(Pair.of(0, 1), Pair.of(0, 2), Pair.of(1, 2), Pair.of(2, 1)); + final SuccinctDirectedGraph s = new SuccinctDirectedGraph(3, edges, false); + assertEquals(2, s.outDegreeOf(0)); + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntDirectedGraphTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntDirectedGraphTest.java new file mode 100644 index 00000000000..342a8a0fe82 --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntDirectedGraphTest.java @@ -0,0 +1,293 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.jgrapht.alg.util.Pair; +import org.jgrapht.generate.GnpRandomGraphGenerator; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.util.SupplierUtil; +import org.junit.jupiter.api.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)); + assertEquals(1, s.getEdge(0, 2)); + assertEquals(2, s.getEdge(1, 2)); + assertEquals(3, s.getEdge(2, 1)); + assertEquals(4, s.getEdge(2, 3)); + assertEquals(5, s.getEdge(3, 0)); + assertEquals(6, s.getEdge(3, 3)); + assertEquals(7, s.getEdge(3, 4)); + assertEquals(8, 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(0, s.getEdgeSource(0)); + assertEquals(1, s.getEdgeTarget(0)); + + assertEquals(0, s.getEdgeSource(1)); + assertEquals(2, s.getEdgeTarget(1)); + + assertEquals(1, s.getEdgeSource(2)); + assertEquals(2, s.getEdgeTarget(2)); + + assertEquals(2, s.getEdgeSource(3)); + assertEquals(1, s.getEdgeTarget(3)); + + assertEquals(2, s.getEdgeSource(4)); + assertEquals(3, s.getEdgeTarget(4)); + + 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)); + assertEquals(1, s.getEdge(2, 0)); + assertEquals(0, s.getEdgeSource(0)); + assertEquals(1, s.getEdgeTarget(0)); + assertEquals(2, s.getEdgeSource(1)); + assertEquals(0, s.getEdgeTarget(1)); + } + + @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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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 testOutgoingOnly() + { + final List> edges = List + .of(Pair.of(0, 1), Pair.of(0, 2), Pair.of(1, 2), Pair.of(2, 1)); + final SuccinctIntDirectedGraph s = new SuccinctIntDirectedGraph(3, edges, false); + assertEquals(2, s.outDegreeOf(0)); + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraphTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraphTest.java new file mode 100644 index 00000000000..09a07d6457c --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctIntUndirectedGraphTest.java @@ -0,0 +1,273 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.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)); + assertEquals(1, s.getEdge(3, 0)); + assertEquals(2, s.getEdge(1, 2)); + assertEquals(3, s.getEdge(4, 1)); + assertEquals(4, s.getEdge(2, 3)); + assertEquals(5, s.getEdge(3, 3)); + assertEquals(6, 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(0, s.getEdgeSource(0)); + assertEquals(1, s.getEdgeTarget(0)); + + assertEquals(0, s.getEdgeSource(1)); + assertEquals(3, s.getEdgeTarget(1)); + + assertEquals(1, s.getEdgeSource(2)); + assertEquals(2, s.getEdgeTarget(2)); + + assertEquals(1, s.getEdgeSource(3)); + assertEquals(4, s.getEdgeTarget(3)); + + assertEquals(2, s.getEdgeSource(4)); + assertEquals(3, s.getEdgeTarget(4)); + + assertEquals(3, s.getEdgeSource(5)); + assertEquals(3, s.getEdgeTarget(5)); + + assertEquals(3, s.getEdgeSource(6)); + assertEquals(4, s.getEdgeTarget(6)); + + 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)); + assertEquals(1, s.getEdge(2, 0)); + assertEquals(0, s.getEdgeSource(0)); + assertEquals(0, s.getEdgeTarget(0)); + assertEquals(0, s.getEdgeSource(1)); + assertEquals(2, s.getEdgeTarget(1)); + } + + @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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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)); + } + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctUndirectedGraphTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctUndirectedGraphTest.java new file mode 100644 index 00000000000..b654a5e87f4 --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/SuccinctUndirectedGraphTest.java @@ -0,0 +1,279 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.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.jupiter.api.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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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(s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e)), e.toString()); + for (final DefaultEdge e : s.edgeSet()) + assertTrue(t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e)), e.toString()); + 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)); + } + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapterTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapterTest.java new file mode 100644 index 00000000000..0a56194e04d --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedBigGraphAdapterTest.java @@ -0,0 +1,296 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.junit.jupiter.api.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)); + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapterTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapterTest.java new file mode 100644 index 00000000000..d8c186ab54e --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableDirectedGraphAdapterTest.java @@ -0,0 +1,291 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.junit.jupiter.api.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)); + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapterTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapterTest.java new file mode 100644 index 00000000000..460281e8199 --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedBigGraphAdapterTest.java @@ -0,0 +1,204 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Collections; + +import org.junit.jupiter.api.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))); + } +} diff --git a/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapterTest.java b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapterTest.java new file mode 100644 index 00000000000..88ece8fdf07 --- /dev/null +++ b/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/ImmutableUndirectedGraphAdapterTest.java @@ -0,0 +1,201 @@ +/* + * (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.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Collections; + +import org.junit.jupiter.api.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))); + } +} diff --git a/license-EPL.txt b/license-EPL.txt new file mode 100644 index 00000000000..e23ece2c852 --- /dev/null +++ b/license-EPL.txt @@ -0,0 +1,277 @@ +Eclipse 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. \ No newline at end of file diff --git a/license-LGPL.txt b/license-LGPL.txt index b1e3f5a2638..fdf882ff24a 100644 --- a/license-LGPL.txt +++ b/license-LGPL.txt @@ -2,7 +2,7 @@ Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + 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. @@ -485,7 +485,7 @@ convey the exclusion of warranty; and each file should have at least the 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 + 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. diff --git a/pom.xml b/pom.xml index d7e0f549cf8..66cfdc491f0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,182 +1,536 @@ - 4.0.0 - net.sf.jgrapht - jgrapht - pom - JGraphT - Parent - 0.8.3-SNAPSHOT - A Java class library for graph-theory data structures and algorithms. - http://www.jgrapht.org - - - GNU Lesser General Public License Version 2.1, February 1999 - http://jgrapht.sourceforge.net/LGPL.html - repo - - - - http://jgrapht.svn.sourceforge.net/svnroot/jgrapht/trunk - scm:svn:https://jgrapht.svn.sourceforge.net/svnroot/jgrapht/trunk - - - http://sourceforge.net/tracker/?group_id=86459 - - - - jgrapht-users - http://lists.sourceforge.net/lists/listinfo/jgrapht-users - - - jgrapht-announce - http://lists.sourceforge.net/lists/listinfo/jgrapht-announce - - - - - John V Sichi - perfecthash@users.sf.net - perfecthash - - - Andrew Newell - ajnewell@users.sf.net - ajnewell - - - Assaf Lehr - assaf-lehr@users.sf.net - assaf-lehr - - - Barak Naveh - barak_naveh@users.sf.net - barak_naveh - - - Michael Behrisch - behrisch@users.sf.net - behrisch - - - Charles Fry - cfry@users.sf.net - cfry - - - Chris Soltenborn - csoltenborn@users.sf.net - csoltenborn - - - Christian Hammer - hammerc@users.sf.net - hammerc - - - Ilya Razenshteyn - ilyaraz@users.sf.net - ilyaraz - - - Hartmut Benz - ivins@users.sf.net - ivins - - - Linda Buisman - linda_buisman@users.sf.net - linda_buisman - - - Liviu Rau - liviu_aurelian@users.sf.net - liviu_aurelian - - - Trevor Harmon - vocaro@users.sf.net - vocaro - - - - - - - UTF-8 - - - - - - ${project.groupId} - jgrapht-core - ${project.version} - - - ${project.groupId} - jgrapht-ext - ${project.version} - - - ${project.groupId} - jgrapht-demo - ${project.version} - - - xmlunit - xmlunit - 1.3 - test - - - junit - junit - 4.10 - test - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - false - - - - - - - - jgrapht-core - jgrapht-ext - jgrapht-demo - jgrapht-dist - - - - - touchgraph - - jgrapht-touchgraph - - - - \ No newline at end of file + 4.0.0 + org.jgrapht + jgrapht + pom + JGraphT - Parent + 1.5.3-SNAPSHOT + 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.2 + + + 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} + 1.14.0 + 10.26.1 + 1.14.0 + 2.7.4 + 8.5.16 + 33.3.1-jre + 2.2 + 11 + 4.2.2 + 0.14 + 1.37 + 5.10.1 + 1.10.1 + 1.0.0 + 3.7.1 + 5.1.9 + 3.6.0 + 3.14.0 + 3.1.4 + 3.2.8 + 3.4.2 + 3.11.2 + 3.1.1 + 3.3.1 + 3.6.0 + 3.3.1 + 3.5.3 + 0.9.0 + 1.5.3 + 5.4.1 + 3.7.1 + 3.6.12 + 2.10.3 + + + + + + ${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} + + + it.unimi.dsi + fastutil + ${fastutil.version} + + + it.unimi.dsi + dsiutils + ${dsiutils.version} + + + com.sun.mail + javax.mail + + + + + it.unimi.dsi + sux4j + ${sux4j.version} + + + it.unimi.dsi + webgraph + ${webgraph.version} + + + it.unimi.dsi + webgraph-big + ${webgraph-big.version} + + + org.jheaps + jheaps + ${jheaps.version} + + + org.apfloat + apfloat + ${apfloat.version} + + + com.github.vlsi.mxgraph + jgraphx + ${jgraphx.version} + + + com.google.guava + guava + ${guava.version} + + + org.apache.commons + commons-text + ${commons-text.version} + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + org.xmlunit + xmlunit-core + ${xmlunit-core.version} + test + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.junit.platform + junit-platform-suite + ${junit.platform.version} + test + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + + --add-modules + java.xml + + + + + default-compile + + + -Xlint:all,-deprecation + -Xpkginfo:always + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + false + + + junit.jupiter.testinstance.lifecycle.default = per_class + junit.jupiter.execution.parallel.enabled = true + + + + + + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + + + junit.jupiter.testinstance.lifecycle.default = per_class + junit.jupiter.execution.parallel.enabled = true + + + + + + org.apache.maven.surefire + surefire-junit-platform + ${maven-surefire-plugin.version} + + + + + integration-test + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.felix + maven-bundle-plugin + ${maven-bundle-plugin.version} + + + + *;-noimport:=true + + + + + process-classes + + manifest + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + forked-path + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + 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 + ${java.version} +

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/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" > + + true + ${java.home}/bin/javadoc + + + + attach-javadocs + + jar + + + + + + + org.eclipse.m2e + lifecycle-mapping + ${lifecycle-mapping.version} + + + + + + + org.apache.felix + + + maven-bundle-plugin + + + [3.0.1,) + + + manifest + + + + + + + + + + + + com.google.code.maven-replacer-plugin + replacer + ${replacer.version} + + + org.sonatype.central + central-publishing-maven-plugin + ${maven-central-plugin.version} + true + + central + https://central.sonatype.com/repository/maven-snapshots/ + true + published + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + + + + 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 + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + 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 + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + + +