├── .Dockerignore ├── .clang-format ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── algorithm-documentation-template.md │ ├── benchmark-algorithm-template.md │ ├── core-class-template.md │ ├── feature_request.md │ └── new-algorithm-template.md ├── dependabot.yml ├── release.yml └── workflows │ ├── documentation.yml │ ├── greetings.yml │ ├── main-ci.yml │ ├── pull-request.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── codecov.yml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── algorithms │ │ ├── clique-detection │ │ │ ├── _category_.json │ │ │ └── bron_kerbosch.md │ │ ├── coloring │ │ │ ├── _category_.json │ │ │ ├── greedy-graph-coloring.md │ │ │ └── welsh-powell.md │ │ ├── cycle-detection │ │ │ ├── _category_.json │ │ │ └── dfs-based.md │ │ ├── intro.md │ │ ├── minimum-spanning-tree │ │ │ ├── _category_.json │ │ │ ├── kruskal.md │ │ │ └── prim.md │ │ ├── shortest-path │ │ │ ├── _category_.json │ │ │ ├── a-star.md │ │ │ ├── bellman-ford.md │ │ │ ├── bfs-based-shortest-path.md │ │ │ ├── dijkstra.md │ │ │ └── floyd-warshall.md │ │ ├── strongly-connected-components │ │ │ ├── _category_.json │ │ │ ├── kosarajus.md │ │ │ └── tarjan.md │ │ ├── topological-sort │ │ │ └── topological-sort.md │ │ └── traversal │ │ │ ├── _category_.json │ │ │ ├── breadth-first-search.md │ │ │ └── depth-first-search.md │ ├── examples │ │ ├── example-basics │ │ │ ├── _category_.json │ │ │ ├── dot-serialization.md │ │ │ ├── shortest-path.md │ │ │ └── transport-example.md │ │ └── intro.md │ └── quickstart │ │ ├── basics │ │ ├── _category_.json │ │ ├── architecture.md │ │ ├── creating-your-first-graph.md │ │ └── using-algorithms.md │ │ ├── installation │ │ ├── _category_.json │ │ ├── alternative-methods.md │ │ └── installation.md │ │ └── intro.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ └── index.tsx ├── static │ ├── .nojekyll │ └── img │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── examples │ │ ├── dot-serialization-graph.png │ │ ├── example_shortest_path_unweighted.png │ │ ├── example_shortest_path_weighted.png │ │ ├── example_traversed_graph_BFS.png │ │ ├── example_usage.png │ │ ├── shortest-path-graph.png │ │ └── shortest_path.png │ │ ├── favicon.ico │ │ ├── graaf.png │ │ ├── graph_example.png │ │ ├── hacktoberfest-logo.png │ │ ├── jetbrains-logo.svg │ │ ├── logo.svg │ │ ├── quickstart │ │ ├── Cycles.png │ │ └── Graph.png │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg ├── tsconfig.json └── yarn.lock ├── examples ├── CMakeLists.txt ├── README.md ├── dot_serialization │ └── main.cpp ├── shortest_path │ └── main.cpp └── transport │ └── main.cpp ├── include ├── README.md └── graaflib │ ├── algorithm │ ├── clique_detection │ │ ├── bron_kerbosch.h │ │ └── bron_kerbosch.tpp │ ├── coloring │ │ ├── greedy_graph_coloring.h │ │ ├── greedy_graph_coloring.tpp │ │ ├── welsh_powell.h │ │ └── welsh_powell.tpp │ ├── cycle_detection │ │ ├── dfs_cycle_detection.h │ │ └── dfs_cycle_detection.tpp │ ├── graph_traversal │ │ ├── breadth_first_search.h │ │ ├── breadth_first_search.tpp │ │ ├── common.h │ │ ├── depth_first_search.h │ │ └── depth_first_search.tpp │ ├── minimum_spanning_tree │ │ ├── kruskal.h │ │ ├── kruskal.tpp │ │ ├── prim.h │ │ └── prim.tpp │ ├── shortest_path │ │ ├── a_star.h │ │ ├── a_star.tpp │ │ ├── bellman_ford.h │ │ ├── bellman_ford.tpp │ │ ├── bfs_shortest_path.h │ │ ├── bfs_shortest_path.tpp │ │ ├── common.h │ │ ├── common.tpp │ │ ├── dijkstra_shortest_path.h │ │ ├── dijkstra_shortest_path.tpp │ │ ├── dijkstra_shortest_paths.h │ │ ├── dijkstra_shortest_paths.tpp │ │ ├── floyd_warshall.h │ │ └── floyd_warshall.tpp │ ├── strongly_connected_components │ │ ├── common.h │ │ ├── kosaraju.h │ │ ├── kosaraju.tpp │ │ ├── tarjan.h │ │ └── tarjan.tpp │ ├── topological_sorting │ │ ├── dfs_topological_sorting.h │ │ └── dfs_topological_sorting.tpp │ ├── utils.h │ └── utils.tpp │ ├── edge.h │ ├── edge.tpp │ ├── graph.h │ ├── graph.tpp │ ├── io │ ├── dot.h │ └── dot.tpp │ ├── properties │ ├── vertex_properties.h │ └── vertex_properties.tpp │ ├── tree.h │ ├── tree.tpp │ └── types.h ├── perf ├── CMakeLists.txt └── graaflib │ ├── add_edge_benchmark.cpp │ ├── add_vertex_benchmark.cpp │ ├── benchmark.cpp │ └── bron_kerbosch_benchmark.cpp ├── test ├── CMakeLists.txt ├── graaflib │ ├── algorithm │ │ ├── clique_detection │ │ │ └── bron_kerbosch_test.cpp │ │ ├── coloring │ │ │ ├── greedy_coloring_test.cpp │ │ │ └── welsh_powell_test.cpp │ │ ├── cycle_detection │ │ │ └── dfs_cycle_detection_test.cpp │ │ ├── graph_traversal │ │ │ ├── breadth_first_search_test.cpp │ │ │ └── depth_first_search_test.cpp │ │ ├── minimum_spanning_tree │ │ │ ├── kruskal_test.cpp │ │ │ └── prim_test.cpp │ │ ├── shortest_path │ │ │ ├── a_star_test.cpp │ │ │ ├── bellman_ford_test.cpp │ │ │ ├── bfs_shortest_path_test.cpp │ │ │ ├── dijkstra_shortest_path_test.cpp │ │ │ ├── dijkstra_shortest_paths_test.cpp │ │ │ └── floyd_warshall_test.cpp │ │ ├── strongly_connected_components │ │ │ ├── kosaraju_test.cpp │ │ │ └── tarjan_test.cpp │ │ ├── topological_sorting │ │ │ └── dfs_topological_sorting_test.cpp │ │ └── utils_test.cpp │ ├── directed_graph_test.cpp │ ├── graph_test.cpp │ ├── graph_traits_test.cpp │ ├── io │ │ └── dot_test.cpp │ ├── properties │ │ └── vertex_properties_test.cpp │ ├── tree_test.cpp │ ├── undirected_graph_test.cpp │ └── weighted_graph_test.cpp └── utils │ ├── fixtures │ └── fixtures.h │ └── scenarios │ ├── scenarios.h │ └── scenarios.tpp └── tools ├── benchmark_visualization.py └── requirements.txt /.Dockerignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | # IDE 4 | .vscode/ 5 | .cache/ 6 | .vs/ 7 | .idea -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/** linguist-vendored -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/algorithm-documentation-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Algorithm Documentation Template 3 | about: Template used for improving the documentation of existing algorithms. 4 | title: "[DOCS]" 5 | labels: documentation, good first issue, help wanted 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Documentation [ALGORITHM_NAME] 11 | The goal of this issue if the improve the documentation of an existing algorithm. This documentation should go into the algorithms section of our [docusaurus docs](https://bobluppes.github.io/graaf/docs/algorithms/intro). 12 | 13 | More detail on how to build the documentation locally can be found on the [wiki](https://github.com/bobluppes/graaf/wiki/development-setup#documentation). 14 | 15 | An existing documentation page exists, which should be extended. The page can be found under `docs/docs/algorithms/`. 16 | OR 17 | A new documentation page should be created under `docs/docs/algorithms`. Potentially, a new subdirectory should be created for the algorithm's category. 18 | 19 | The corresponding algorithm is called `` and can be found under `include/graaflib/algorithm/`. 20 | 21 | ## Documentation Contents 22 | The documentation entry should adhere to the following template: 23 | 24 | ```markdown 25 | # [ALGORITHM_NAME] 26 | - A short description of the algorithm, what does it do and in which use cases is it used. 27 | - What are the limitations of the algorithm, does it work on both directed and undirected graphs? Does it consider edge weights, and if yes, does it support negative weights? 28 | - Link to the wikipedia entry on the algorithm. 29 | 30 | ## Syntax 31 | - A code block with the syntax of the algorithm. This should now include any javadoc comments. 32 | - An explanation of the parameters (including template parameters) and the return type. 33 | 34 | ## (Optional) See also 35 | - If there are similar algorithms, or we have a slightly different version of the same algorithm, you can link it here. 36 | - If we have an example (found on the example page of the docs) which uses this algorithm, you can link it here. 37 | ``` 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/benchmark-algorithm-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Benchmark Algorithm Template 3 | about: Template used for creating benchmark issues. 4 | title: "[BENCH]" 5 | labels: good first issue, help wanted, performance 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Benchmark [ALGORITHM NAME] 11 | The goal of this issues is to add a benchmark for the `[ALGORITHM NAME]` algorithm. The algorithm implementation is located under `include/graaflib/algorithm/`. Benchmarks are vital to our library and allow us to measure the impact of future performance improvements. 12 | 13 | We use the [Google benchmark](https://github.com/google/benchmark/blob/main/docs/user_guide.md) framework. For inspiration, please take a look at the existing benchmarks in `/perf`. 14 | 15 | The benchmark should be added under `/perf` in a directory which resembles the file structure of the original algorithm. i.e. if the algorithm is implemented in `include/graaflib/algorithm/coloring/greedy_graph_coloring.h` then the benchmark should be added to `perf/graaflib/algorithm/coloring/greedy_graph_coloring_benchmark.cpp`. 16 | 17 | The benchmark should measure the runtime performance of the algorithm for increasing input sizes. 18 | 19 | ## Running Benchmarks 20 | If you IDE has the necessary integrations for it, all benchmarks can be run in the IDE from the `perf/graaflib/benchmark.cpp` file. 21 | 22 | Otherwise, we can run the benchmarks from the command line: 23 | ```bash 24 | # run all benchmarks 25 | cd build/perf && ./Graaf_perf 26 | ``` 27 | 28 | To run an individual benchmark: 29 | ```bash 30 | ./Graaf_perf --benchmark_filter=YOUR_BENCHMARK_NAME 31 | ``` 32 | 33 | For more options, pass the `--help` flag. 34 | 35 | 36 | ## Definition of Done 37 | - [ ] A benchmark is added for the algorithm 38 | - [ ] Benchmark results (copy past of the output is fine) is added to the PR 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/core-class-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Core Class Template 3 | about: Template used for new core classes in the Graaf library. 4 | title: "[CORE]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | # [CLASS_NAME] 11 | A short description of what the class does and it's common use cases. 12 | 13 | ## Syntax 14 | An initial idea for the (public) interface of the new class is as follows:
15 | _(note that this is not set in store and merely serves as a starting point)_ 16 | 17 | ```c++ 18 | class my_class { 19 | public: 20 | //... 21 | } 22 | ``` 23 | 24 | This class should live in the `graaf` namespace under `include/graaflib/`. 25 | 26 | ## Definition of Done 27 | This issue is done when: 28 | 29 | - [ ] The new class is implemented 30 | - [ ] The new class has a javadoc-style comment for the entire class and for the public methods 31 | - [ ] Appropriate tests are added under `test/graaflib/algorithm` 32 | - [ ] A test coverage of at least 95% is reached 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEAT]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Request 11 | 12 | ### Why is this important? 13 | Explain why this feature is important for the library and how it can benefit the users. 14 | 15 | ### Describe the solution you'd like 16 | A clear and concise description of what you want to happen. 17 | 18 | ### Describe alternatives you've considered 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ### Additional context 22 | Add any other context or screenshots about the feature request here. 23 | 24 | ### Are you willing to contribute? 25 | Let us know if you are willing to contribute to the development of this feature. 26 | 27 | ### Checklist 28 | Please check the following before submitting the issue: 29 | 30 | - [ ] I have searched for similar feature requests in the issues. 31 | - [ ] This feature is not already implemented in the library. 32 | - [ ] I have provided a clear and concise description of the feature request. 33 | - [ ] I have explained why this feature is important and how it benefits the library and users. 34 | - [ ] I am willing to contribute to the development of this feature (if applicable). 35 | 36 | Please note that feature requests are subject to review and may or may not be implemented in the library. 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-algorithm-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Algorithm Template 3 | about: Template used for new algorithms in the Graaf library. 4 | title: "[ALGO]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | # [ALGORITHM_NAME] 11 | A short description of what the algorithm does and it's common use cases. 12 | 13 | ## Syntax 14 | The algorithm should have the following syntax: 15 | 16 | ```c++ 17 | template 18 | [[nodiscard]] return_type your_algorithm_name(const graph& graph); 19 | ``` 20 | 21 | This should live in the `graaf::algorithm` namespace under `include/graaflib/algorithm`. 22 | 23 | ## Definition of Done 24 | This issue is done when: 25 | 26 | - [ ] The algorithm is implemented 27 | - [ ] The new function has a javadoc-style comment explaining the interface 28 | - [ ] Appropriate tests are added under `test/graaflib/algorithm` 29 | - [ ] A test coverage of at least 95% is reached 30 | - [ ] A documentation entry is added under `docs/docs/algorithms` under the appropriate category 31 | - Just adding a short description and the algorithm syntax here is fine 32 | - See the [wiki](https://github.com/bobluppes/graaf/wiki/development-setup#documentation) on how to build the documentation locally 33 | - [ ] The algorithm is added to the list of algorithms in `README.md` 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "npm" 9 | # Look for `package.json` and `lock` files in the `root` directory 10 | directory: "/docs" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - octocat 7 | categories: 8 | - title: Breaking Changes 🛠 9 | labels: 10 | - breaking-change 11 | - title: Exciting New Features 🎉 12 | labels: 13 | - enhancement 14 | - title: Documentation changes 15 | labels: 16 | - documentation 17 | - tests 18 | - title: Other changes 19 | labels: 20 | - refactor 21 | - tooling 22 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Graaf repository 13 | uses: actions/checkout@v4 14 | 15 | # Node is required for npm 16 | - name: Set up Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: "18" 20 | 21 | - name: Build Docusaurus website 22 | run: | 23 | cd docs 24 | npm install 25 | npm run build 26 | 27 | - name: Deploy to GitHub Pages 28 | if: success() 29 | uses: crazy-max/ghaction-github-pages@v4 30 | with: 31 | target_branch: gh-pages 32 | build_dir: docs/build 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: "Hi there! Thank you for creating your first issue on the Graaf library, we will look into it shortly. In the mean time, please make sure the issue has the correct labels set." 16 | pr-message: "Hi there! Thank you for creating your first pull-request on the Graaf library :)" 17 | -------------------------------------------------------------------------------- /.github/workflows/main-ci.yml: -------------------------------------------------------------------------------- 1 | name: Main-CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | env: 8 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 9 | BUILD_TYPE: Debug 10 | 11 | jobs: 12 | coverage: 13 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 14 | # You can convert this to a matrix build if you need cross-platform coverage. 15 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 16 | runs-on: ubuntu-22.04 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install dependencies 22 | run: sudo apt-get install lcov -y 23 | 24 | - name: Pull CMake modules 25 | uses: actions/checkout@v4 26 | with: 27 | repository: bilke/cmake-modules 28 | ref: 877bab9dd1b17468c5d939cacaa2ad7ba99d1977 29 | path: cmake-modules 30 | 31 | - name: Configure CMake 32 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 33 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 34 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_COVERAGE=True 35 | 36 | - name: Generate coverage XML 37 | working-directory: ${{github.workspace}}/build 38 | run: make ctest_coverage 39 | 40 | - name: Upload coverage report 41 | uses: codecov/codecov-action@v5 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | files: ./build/ctest_coverage.info 45 | verbose: true # optional (default = false) 46 | 47 | format: 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v4 52 | 53 | - name: clang-format 54 | uses: jidicula/clang-format-action@v4.14.0 55 | with: 56 | clang-format-version: '15' 57 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull-Request-CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | 7 | env: 8 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 9 | BUILD_TYPE: Debug 10 | 11 | jobs: 12 | compile_and_test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | compiler: [g++, clang++] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Configure CMake 22 | run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 23 | 24 | - name: Compile 25 | working-directory: ${{github.workspace}}/build 26 | run: cmake --build . 27 | 28 | - name: Test 29 | working-directory: ${{github.workspace}}/build 30 | run: ctest 31 | 32 | 33 | coverage: 34 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 35 | # You can convert this to a matrix build if you need cross-platform coverage. 36 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 37 | runs-on: ubuntu-22.04 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | 42 | - name: Install dependencies 43 | run: sudo apt-get install lcov -y 44 | 45 | - name: Pull CMake modules 46 | uses: actions/checkout@v4 47 | with: 48 | repository: bilke/cmake-modules 49 | ref: 877bab9dd1b17468c5d939cacaa2ad7ba99d1977 50 | path: cmake-modules 51 | 52 | - name: Configure CMake 53 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 54 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 55 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_COVERAGE=True 56 | 57 | - name: Generate coverage XML 58 | working-directory: ${{github.workspace}}/build 59 | run: make ctest_coverage 60 | 61 | - name: Upload coverage report 62 | uses: codecov/codecov-action@v5 63 | with: 64 | token: ${{ secrets.CODECOV_TOKEN }} 65 | files: ./build/ctest_coverage.info 66 | verbose: true # optional (default = false) 67 | 68 | format: 69 | runs-on: ubuntu-latest 70 | 71 | steps: 72 | - uses: actions/checkout@v4 73 | 74 | - name: clang-format 75 | uses: jidicula/clang-format-action@v4.14.0 76 | with: 77 | clang-format-version: '15' 78 | 79 | verify_documentation: 80 | runs-on: ubuntu-latest 81 | 82 | steps: 83 | - uses: actions/checkout@v4 84 | 85 | # Node is required for npm 86 | - name: Set up Node 87 | uses: actions/setup-node@v3 88 | with: 89 | node-version: "18" 90 | 91 | - name: Build Docusaurus website 92 | run: | 93 | cd docs 94 | npm install 95 | npm run build 96 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" # Push events to matching v*.*.*, i.e. v2.1.0 7 | 8 | jobs: 9 | create-release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Install dependencies 15 | run: sudo apt-get install tar zip -y 16 | - name: Create tarball 17 | run: tar -czvf header-only.tar.gz include 18 | - name: Create zip 19 | run: zip -r header-only.zip include 20 | - name: Release 21 | uses: softprops/action-gh-release@v2 22 | with: 23 | files: | 24 | header-only.zip 25 | header-only.tar.gz 26 | generate_release_notes: true 27 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '0 9 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v9 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | days-before-stale: 30 25 | days-before-close: -1 26 | stale-issue-message: | 27 | Marking this issue as stale. It will **not** be automatically closed. 28 | 29 | Even though the maintainers of Graaf may not always have time to take a look in a timely fashion, your contributions are much appreciated. 30 | Please allow some time for @bobluppes to take a closer look. 31 | stale-pr-message: | 32 | Marking this PR as stale. It will **not** be automatically closed. 33 | 34 | Even though the maintainers of Graaf may not always have time to take a look in a timely fashion, your contributions are much appreciated. 35 | Please allow some time for @bobluppes to take a closer look. 36 | stale-issue-label: 'stale' 37 | stale-pr-label: 'stale' 38 | exempt-pr-labels: 'dependencies' 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cmake-modules/ 3 | venv 4 | cmake-build-debug 5 | 6 | # IDE 7 | .vscode/ 8 | .cache/ 9 | .vs/ 10 | .idea -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11...3.25) 2 | 3 | project(Graaf 4 | VERSION 0.1.0 5 | DESCRIPTION "A light-weight C++ graph library." 6 | LANGUAGES C CXX 7 | ) 8 | 9 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 10 | 11 | # This project uses C++ 20 features 12 | set(CMAKE_CXX_STANDARD 20) 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | 15 | # The target name of the Library, what name you give to the Interface 16 | set(GRAAF_LIB_TARGET_NAME ${PROJECT_NAME}) 17 | 18 | # Example usages of the library 19 | option(SKIP_EXAMPLES "Skip building the examples" OFF) 20 | if(NOT SKIP_EXAMPLES) 21 | add_subdirectory(examples) 22 | endif() 23 | 24 | # Enables testing in CMAKE, needs to be called BEFORE call to add_subdirectory 25 | option(SKIP_TESTS "Skip building the tests" OFF) 26 | if(NOT SKIP_TESTS) 27 | enable_testing() 28 | add_subdirectory(test) 29 | endif() 30 | 31 | # Benchmarks 32 | option(SKIP_BENCHMARKS "Skip building the performance benchmarks" OFF) 33 | if(NOT SKIP_BENCHMARKS) 34 | add_subdirectory(perf) 35 | endif() 36 | 37 | #Adding Interface to enable use of FetchContent 38 | add_library(${GRAAF_LIB_TARGET_NAME} INTERFACE) 39 | add_library(${PROJECT_NAME}::${GRAAF_LIB_TARGET_NAME} ALIAS ${PROJECT_NAME}) 40 | 41 | target_include_directories(${GRAAF_LIB_TARGET_NAME} INTERFACE "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/") -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for considering contributing to Graaf! To ensure a smooth collaboration process, please take a moment to review the guidelines. 2 | 3 | > :warning: For more in-depth details on contributing to Graaf, take a look at the [**wiki**](https://github.com/bobluppes/graaf/wiki). 4 | 5 | ## Ways to Contribute 6 | - **Bug reports**: If you encounter a bug or unexpected behavior while using Graaf, please open a new issue on our [issue tracker](https://github.com/bobluppes/graaf/issues). Include a clear description of the problem, steps to reproduce it, and any relevant details. 7 | - **Feature requests**: If you have a feature idea or enhancement suggestion for Graaf, we encourage you to submit a new issue on the [issue tracker](https://github.com/bobluppes/graaf/issues). Describe the desired functionality and provide any relevant context that could help us understand the request. 8 | - **Pull requests**: If you have implemented a bug fix, added a new feature, or made any other improvements to Graaf, feel free to submit a pull request. Please ensure that your code follows our coding conventions and include tests and documentation when applicable. 9 | 10 | ## Code of Conduct 11 | Please note that we have a [Code of Conduct](https://github.com/bobluppes/graaf/wiki/code-of-conduct) in place to ensure a respectful and inclusive community environment. By participating in the Graaf project, you agree to abide by its terms. Instances of abusive, harassing, or otherwise unacceptable behavior should be reported to the project maintainers. 12 | 13 | Thank you for your interest in contributing to Graaf! Your contributions are greatly appreciated and help make the library better for everyone. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # Set environment variables to non-interactive (this prevents some prompts) 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | # Install Clang, CMake, git, and other required tools 7 | RUN apt-get update && apt-get install -y \ 8 | clang \ 9 | cmake \ 10 | make \ 11 | git \ 12 | libstdc++-10-dev && \ 13 | apt-get clean && \ 14 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 15 | 16 | # Set the working directory 17 | WORKDIR /usr/src/graaf 18 | 19 | # Copy the entire project to the working directory 20 | COPY . . 21 | 22 | # Build the project using CMake and Clang 23 | RUN mkdir build && cd build && \ 24 | cmake .. && \ 25 | cmake --build . 26 | 27 | CMD ["/bin/bash"] 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bob Luppes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 95% 6 | threshold: 5% 7 | patch: 8 | default: 9 | target: 95% -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .docusaurus/ 2 | build/ 3 | node_modules/ 4 | 5 | yarn-error.log -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 3](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/algorithms/clique-detection/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Clique Detection", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/docs/algorithms/clique-detection/bron_kerbosch.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Bron-Kerbosch algorithm 6 | 7 | Bron-Kerbosch algorithm finding all maximal cliques in an undirected graph. A clique is a subset of vertices such that 8 | every two distinct vertices are adjacent to each other. 9 | The maximal clique is the subset of vertices of an undirected graph where no additional vertex can be added 10 | due to the complete connectivity rule. The algorithm lists all maximum cliques of an undirected graph. 11 | 12 | The worst-case run time of the algorithm is 3V/3. 13 | [wikipedia](https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm#Worst-case_analysis) 14 | 15 | ## Syntax 16 | 17 | ```cpp 18 | template 19 | std::vector> bron_kerbosch( 20 | const graph& graph); 21 | ``` 22 | 23 | - **graph** The graph to extract maximal cliques. 24 | - **return** Returns 2D vector of vertices each vector represent set of vertices that form clique. 25 | 26 | -------------------------------------------------------------------------------- /docs/docs/algorithms/coloring/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Graph Coloring Algorithms", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/algorithms/coloring/greedy-graph-coloring.md: -------------------------------------------------------------------------------- 1 | # Greedy Graph Coloring Algorithm 2 | 3 | Greedy Graph Coloring computes a coloring of the vertices of a (simple, connected) graph such that no two adjacent 4 | vertices have the same color. 5 | 6 | If the graph has different connected components, each component will be treated as a separate simple connected graph. 7 | 8 | The algorithm is heuristic and does not guarantee an optimal number of different colors (that is, equal to the chromatic 9 | number of a simple, connected graph). 10 | 11 | Colors are represented by the numbers 0, 1, 2,... The greedy algorithm considers the vertices of the graph in sequence 12 | and assigns each vertex its first available color, i.e. the color with the smallest number that is not already used by 13 | one of its neighbors. 14 | 15 | The overall worst-case time complexity of the algorithm is `O(n^2)`. In cases where the graph has a fixed degree (a 16 | constant number of neighbors for each vertex), the time complexity can be approximated as `O(n)`. However, if the graph 17 | is highly connected (dense) and approaches a complete graph, the time complexity could approach `O(n^2)`. 18 | 19 | If no coloring is possible, an empty `unordered_map` is returned. This is the case when the graph contains no vertices. 20 | 21 | [wikipedia](https://en.wikipedia.org/wiki/Greedy_coloring) 22 | 23 | ## Syntax 24 | 25 | ```cpp 26 | template 27 | std::unordered_map greedy_graph_coloring(const GRAPH& graph); 28 | ``` 29 | 30 | - **graph** A graph to perform graph coloring on. 31 | - **return** An unordered_map where keys are vertex identifiers and values are their respective colors. If no coloring 32 | is possible, an empty `unordered_map` is returned. -------------------------------------------------------------------------------- /docs/docs/algorithms/coloring/welsh-powell.md: -------------------------------------------------------------------------------- 1 | # Welsh Powell Algorithm 2 | 3 | Welsh Powell Algorithm computes a coloring of the vertices of a (simple, connected) graph such that no two adjacent 4 | vertices have the same color. 5 | 6 | If the graph has different connected components, each component will be treated as a separate simple connected graph. 7 | 8 | The algorithm is heuristic and does not guarantee an optimal number of different colors (that is, equal to the chromatic 9 | number of a simple, connected graph). 10 | 11 | Colors are represented by the numbers 0, 1, 2,... The Welsh Powell algorithm considers the vertices of the graph in 12 | descending order of their degrees and assigns each vertex with its first available color, i.e. the color with the 13 | smallest number that is not already used by one of its neighbors. 14 | 15 | The overall worst-case time complexity of the algorithm is `O(n^2)`. In cases where the graph has a fixed degree (a 16 | constant number of neighbors for each vertex), the time complexity can be approximated as `O(n)`. However, if the graph 17 | is highly connected (dense) and approaches a complete graph, the time complexity could approach `O(n^2)`. 18 | 19 | If no coloring is possible, an empty `unordered_map` is returned. This is the case when the graph contains no vertices. 20 | 21 | [Tutorials Point](https://www.tutorialspoint.com/welsh-powell-graph-colouring-algorithm) 22 | 23 | ## Syntax 24 | 25 | ```cpp 26 | template 27 | std::unordered_map welsh_powell_coloring(const GRAPH& graph); 28 | ``` 29 | 30 | - **graph** A graph to perform graph coloring on. 31 | - **return** An unordered_map where keys are vertex identifiers and values are their respective colors. If no coloring 32 | is possible, an empty `unordered_map` is returned. -------------------------------------------------------------------------------- /docs/docs/algorithms/cycle-detection/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Cycle Detection Algorithms", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/algorithms/cycle-detection/dfs-based.md: -------------------------------------------------------------------------------- 1 | # DFS Based Cycle Detection 2 | 3 | A DFS based cycle detection algorithm is used to identify cycles in graphs, both directed and undirected. 4 | The algorithm can be used to detect cycles in the structure of a graph, as it does not consider edge weights. 5 | 6 | # Directed graph 7 | 8 | The key idea is that when a vertex is processed, mark it as: UNVISITED, VISITED and NO_CYCLE. 9 | By default all vertices marked as UNVISITED. During the traversal, we label vertices as VISITED. At the exit of the 10 | recursion, we label the vertex as NO CYCLE. 11 | If we met a vertex labeled VISITED, we found a cycle in the graph. 12 | 13 | # Undirected graph 14 | 15 | The key idea is to store the parent of each vertex during the traversal. So when we check neighboring vertices, we skip 16 | back edge. 17 | During the traversal we mark the vertex as visited and continue the traversal. In case a vertex was visited before and 18 | vertices have different parents, we found a cycle. 19 | 20 | The runtime of the algorithm is `O(|V| + |E|)` and memory consumption is `O(|V|)`. Where V is the number of vertices in 21 | the graph and E the number of edges. 22 | The algorithm uses DFS traversal and therefore suffers the same limitations (see depth-first-search.md). 23 | 24 | # Use cases 25 | 26 | - Resource dependencies: 27 | - Redundant connections. 28 | - Deadlocks in concurrent systems. 29 | - Deadlocks in concurrent systems. 30 | - Logical dependencies: 31 | - Data base relation. 32 | - Dependency management. 33 | - Circuit design. 34 | - Infinity loops. 35 | 36 | [wikipedia](https://en.wikipedia.org/wiki/Cycle_(graph_theory)#Cycle_detection) 37 | 38 | ## Syntax 39 | 40 | Cycle detection for directed graph. 41 | 42 | ```cpp 43 | template 44 | [[nodiscard]] bool dfs_cycle_detection( 45 | const graph &graph); 46 | 47 | ``` 48 | 49 | Cycle detection for unidrected graph. 50 | 51 | ```cpp 52 | template 53 | [[nodiscard]] bool dfs_cycle_detection( 54 | const graph &graph); 55 | ``` 56 | 57 | - **graph** The graph to traverse. 58 | - **return** Returns true in case of cycle otherwise returns false. 59 | 60 | ## Similar algorithms 61 | 62 | There are many algorithms for cycle detection or algorithms with specific cycle conditions. 63 | See [wikipedia](https://en.wikipedia.org/wiki/Cycle_(graph_theory)#Graph_classes_defined_by_cycle) 64 | -------------------------------------------------------------------------------- /docs/docs/algorithms/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Algorithms Overview 6 | This section provides an overview of the algorithms currently implemented in Graaf. -------------------------------------------------------------------------------- /docs/docs/algorithms/minimum-spanning-tree/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Minimum Spanning Tree", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/algorithms/minimum-spanning-tree/kruskal.md: -------------------------------------------------------------------------------- 1 | # Kruskal's Algorithm 2 | 3 | Kruskal's algorithm finds the minimum spanning forest of an undirected edge-weighted graph. If the graph is connected, 4 | it finds a minimum spanning tree. 5 | The algorithm is implemented with disjoint set union and finding minimum weighted edges. 6 | Worst-case performance is `O(|E|log|V|)`, where `|E|` is the number of edges and `|V|` is the number of vertices in the 7 | graph. Memory usage is `O(V+E)` for maintaining vertices (DSU) and edges. 8 | 9 | [wikipedia](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) 10 | 11 | ## Syntax 12 | 13 | Calculates the shortest path with the minimum edge sum. 14 | 15 | ```cpp 16 | template 17 | [[nodiscard]] std::vector kruskal_minimum_spanning_tree( 18 | const graph& graph); 19 | ``` 20 | 21 | - **graph** The graph to extract MST or MSF. 22 | - **return** Returns a vector of edges that form MST if the graph is connected, otherwise it returns the minimum 23 | spanning forest. 24 | 25 | ### Special case 26 | 27 | In case of multiply edges with same weight leading to a vertex, prioritizing vertices with lesser vertex number. 28 | 29 | ```cpp 30 | std::sort(edges_to_process.begin(), edges_to_process.end(), 31 | [](detail::edge_to_process& e1, 32 | detail::edge_to_process& e2) { 33 | if (e1 != e2) 34 | return e1.get_weight() < e2.get_weight(); 35 | return e1.vertex_a < e2.vertex_a || e1.vertex_b < e2.vertex_b; 36 | }); 37 | ``` 38 | 39 | For custom type edge, we should provide < and != operators 40 | 41 | ```cpp 42 | struct custom_edge : public graaf::weighted_edge { 43 | public: 44 | int weight_{}; 45 | 46 | [[nodiscard]] int get_weight() const noexcept override { return weight_; } 47 | 48 | custom_edge(int weight): weight_{weight} {}; 49 | custom_edge(){}; 50 | ~custom_edge(){}; 51 | 52 | // Providing '<' and '!=' operators for sorting edges 53 | bool operator<(const custom_edge& e) const noexcept { 54 | return this->weight_ < e.weight_; 55 | } 56 | bool operator!=(const custom_edge& e) const noexcept { 57 | return this->weight_ != e.weight_; 58 | } 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/docs/algorithms/minimum-spanning-tree/prim.md: -------------------------------------------------------------------------------- 1 | # Prim's Algorithm 2 | 3 | Prim's algorithm computes the minimum spanning tree (MST) of a connected, undirected graph with weighted edges. Starting 4 | with an arbitrary vertex, the algorithm iteratively selects the edge with the smallest weight that connects a 5 | vertex in the tree to a vertex outside the tree, adding it to the MST. 6 | 7 | The algorithm's worst-case time complexity is O(∣E∣log∣V∣). 8 | 9 | Unlike Kruskal's algorithm, Prim's algorithm works efficiently on dense graphs. A limitation is that it requires the 10 | graph to be connected and does not handle disconnected graphs or graphs with negative-weight cycles. 11 | 12 | Prim's MST is often used in network design, such as electrical wiring and telecommunications. 13 | 14 | [wikipedia](https://en.wikipedia.org/wiki/Prim%27s_algorithm) 15 | 16 | ## Syntax 17 | 18 | ```cpp 19 | template 20 | [[nodiscard]] std::optional > prim_minimum_spanning_tree( 21 | const graph& graph, vertex_id_t start_vertex); 22 | ``` 23 | 24 | - **graph** The undirected graph for which we want to compute the MST. 25 | - **start_vertex** The vertex ID which should be the root of the MST. 26 | - **return** Returns a vector of edges that form MST if the graph is connected, otherwise returns an empty optional. -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Shortest Path Algorithms", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/a-star.md: -------------------------------------------------------------------------------- 1 | # A* Search Algorithm 2 | 3 | A\* computes the shortest path between a starting vertex and a target vertex in weighted and unweighted graphs. 4 | It can be seen as an extension of Dijkstra's classical shortest paths algorithm. The implementation of A\* also tries to 5 | follow `dijkstra_shortest_path` closely where appropriate. Compared to Dijkstra's algorithm, A\* only finds the shortest 6 | path from a start vertex to a target vertex, and not the shortest path to all possible target vertices. Another 7 | difference is that A\* uses a heuristic function to achieve better performance. 8 | 9 | At each iteration of its main loop, A\* needs to determine which of its paths to extend. It does so by minimizing the 10 | so-called `f_score`. 11 | 12 | In A\*, the `f_score` represents the estimated total cost of the path from the start vertex to the goal vertex through 13 | the current vertex. It's a combination of two components: 14 | 15 | 1. `g_score`: The actual cost of the path from the start vertex to the current vertex. 16 | 2. `h_score` (heuristic score): An estimate of the cost required from the current vertex to the goal vertex. 17 | 18 | A\* tries to minimize the `f_score` for each vertex as it explores the graph. The idea is to prioritize exploring 19 | vertices that have lower `f_score` values, as they are expected to lead to potentially better paths. 20 | 21 | Mathematically, `f_score` is often defined as: 22 | 23 | ``` 24 | f_score = g_score + h_score 25 | ``` 26 | 27 | Where: 28 | 29 | - `g_score` is the cost of the path from the start vertex to the current vertex. 30 | - `h_score` is the heuristic estimate of the cost from the current vertex to the goal vertex. 31 | 32 | In the implementation, the heuristic function `heuristic` provides an estimate of `h_score` for each vertex, and the 33 | actual cost of the path from the start vertex to the current vertex is stored in the `g_score` unordered map, as the 34 | algorithm progresses. 35 | 36 | In the implementation, `dist_from_start` from path_vertex represents the `f_score` of the path. 37 | 38 | The time complexity of A\* depends on the provided heuristic function. In the worst case of an unbounded search space, 39 | the number of nodes expanded is exponential in the depth of the solution (the shortest path) `d`. This can be expressed 40 | as `O(b^d)`, where `b` is the branching factor (the average number of successors per state) per stage. 41 | 42 | In weighted graphs, edge weights should be non-negative. Like in the implementation of Dijkstra's algorithm, A\* is 43 | implemented with the priority queue provided by C++, to perform the repeated selection of minimum (estimated) cost nodes 44 | to expand. This is the `open_set`. If the shortest path is not unique, one of the shortest paths is returned. 45 | 46 | * [wikipedia](https://en.wikipedia.org/wiki/A*_search_algorithm) 47 | * [Red Blob Games](https://www.redblobgames.com/pathfinding/a-star/introduction.html) 48 | 49 | ## Syntax 50 | 51 | calculates the shortest path between on start_vertex and one end_vertex using A\* search. 52 | Works on both weighted as well as unweighted graphs. For unweighted graphs, a unit weight is used for each edge. 53 | 54 | ```cpp 55 | template ()))> 56 | requires std::is_invocable_r_v 57 | std::optional> a_star_search( 58 | const graph &graph, vertex_id_t start_vertex, vertex_id_t target_vertex, 59 | const HEURISTIC_T &heuristic); 60 | ``` 61 | 62 | - **graph** The graph to extract shortest path from. 63 | - **start_vertex** The vertex id where the shortest path should should start. 64 | - **target_vertex** The vertex id where the shortest path should end. 65 | - **heuristic** A heuristic function estimating the cost from a vertex to the target. 66 | - **return** An optional containing the shortest path (a list of vertices) if found, or std::nullopt if no such path 67 | exists. -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/bellman-ford.md: -------------------------------------------------------------------------------- 1 | # Bellman-Ford Shortest Path 2 | 3 | Bellman-Ford's algorithm computes shortest paths from a single source vertex to all of the other vertices in weighted 4 | graph and unweighted graphs. In weighted graphs, edge weights are allowed to be negative. Bellman-Ford's algorithm runs 5 | in `O(|E||V|)` for connected graphs, where `|E|` is the number of edges and `|V|` the number of vertices in the 6 | graph. 7 | 8 | A limitation is that this implementation doesn't check for negative-weight cycles. 9 | 10 | [wikipedia](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm) 11 | 12 | ## Syntax 13 | 14 | Find the shortest paths from a source vertex to all other vertices using the Bellman-Ford algorithm. 15 | 16 | ```cpp 17 | template ()))> 19 | std::unordered_map> 20 | bellman_ford_shortest_paths(const graph& graph, vertex_id_t start_vertex); 21 | ``` 22 | 23 | - **graph** The graph to extract shortest path from. 24 | - **start_vertex** The source vertex for the shortest paths. 25 | - **return** A map of target vertex IDs to shortest path structures. 26 | Each value contains a graph_path object representing the shortest path from the source vertex to the respective 27 | vertex. 28 | If a vertex is unreachable from the source, its entry will be absent from the map. -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/bfs-based-shortest-path.md: -------------------------------------------------------------------------------- 1 | # BFS Based Shortest Path 2 | 3 | Breadth-First Search (BFS) is a graph traversal algorithm that efficiently finds the shortest 4 | path between two vertices in an **unweighted graph** by exploring vertices level by level, 5 | guaranteeing the shortest path, and has a time complexity of `O(|E| + |V|)`, 6 | where `|V|` is the number of vertices and `|E|` is the number of edges in the graph. 7 | BFS uses a queue to iteratively visit neighboring vertices from the source 8 | vertex, ensuring that the shortest path is discovered before longer paths. 9 | 10 | [wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 11 | 12 | ## Syntax 13 | 14 | Calculates the shortest path between one start_vertex and one 15 | end_vertex using BFS. This does not consider edge weights. 16 | 17 | ```cpp 18 | template ()))> 19 | std::optional> bfs_shortest_path( 20 | const graph& graph, vertex_id_t start_vertex, vertex_id_t end_vertex); 21 | ``` 22 | 23 | - **graph** The graph to extract shortest path from. 24 | - **start_vertex** Vertex id where the shortest path should start. 25 | - **end_vertex** Vertex id where the shortest path should end. 26 | - **return** An optional with the shortest path (list of vertices) if found. -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/dijkstra.md: -------------------------------------------------------------------------------- 1 | # Dijkstra Shortest Path 2 | 3 | Dijkstra's algorithm computes shortest paths between nodes in weighted and unweighted graphs. In weighted graphs, 4 | edge weights should be non-negative. Dijkstra's algorithm is implemented with a priority queue and runs 5 | in `O(|E|log|V|)` for connected graphs, where `|E|` is the number of edges and `|V|` the number of vertices in the 6 | graph. 7 | 8 | [wikipedia](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) 9 | 10 | ## Syntax 11 | 12 | calculates the shortest path between on start_vertex and one end_vertex using Dijkstra's algorithm. Works on both 13 | weighted as well as unweighted graphs. For unweighted graphs, a unit weight is used for each edge. 14 | 15 | ```cpp 16 | template ()))> 17 | std::optional> 18 | dijkstra_shortest_path(const graph& graph, vertex_id_t start_vertex, vertex_id_t end_vertex); 19 | ``` 20 | 21 | - **graph** The graph to extract shortest path from. 22 | - **start_vertex** Vertex id where the shortest path should start. 23 | - **end_vertex** Vertex id where the shortest path should end. 24 | - **return** An optional with the shortest path (list of vertices) if found. 25 | 26 | Find the shortest paths from a source vertex to all other vertices in the graph using Dijkstra's algorithm. 27 | 28 | ```cpp 29 | template ()))> 30 | [[nodiscard]] std::unordered_map> 31 | dijkstra_shortest_paths(const graph& graph, vertex_id_t source_vertex); 32 | ``` 33 | 34 | - **graph** The graph we want to search. 35 | - **source_vertex** The source vertex from which to compute shortest paths. 36 | - **return** A map containing the shortest paths from the source vertex to all other vertices. The map keys are target 37 | vertex IDs, and the values are instances of graph_path, representing the shortest distance and the path (list of 38 | vertex IDs) from the source to the target. If a vertex is not reachable from the source, its entry will be absent from 39 | the map. -------------------------------------------------------------------------------- /docs/docs/algorithms/shortest-path/floyd-warshall.md: -------------------------------------------------------------------------------- 1 | # Floyd-Warshall algorithm 2 | 3 | Floyd-Warshall algorithm computes the shortest path between any two vertices in a graph, both directed and undirected. 4 | The algorithm does not work for graphs with negative weight cycles. 5 | The key idea of the algorithm is to relax the weighted shortest path between any two vertices, using any vertex as an 6 | intermediate one. 7 | Advantage of the algorithm is that it processes vertices instead of edges. This advantage can be used when the number of 8 | edges is large enough, aka a dense graph. 9 | Runtime of the algorithm is O(|V3|) and memory consumption is O(|V2|). 10 | 11 | [wikipedia](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) 12 | 13 | ## Syntax 14 | 15 | Calculates the shortest path between any two vertices. 16 | 17 | ```cpp 18 | template ()))> 20 | std::vector> floyd_warshall_shortest_paths( 21 | const graph& graph); 22 | ``` 23 | 24 | - **graph** The graph to extract the shortest path from. 25 | - **return** Returns a 2D vector of the shortest path. If a path doesn't exist between two vertices, mark it as 26 | TYPE_MAX. -------------------------------------------------------------------------------- /docs/docs/algorithms/strongly-connected-components/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Strongly Connected Component Algorithms", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/algorithms/strongly-connected-components/kosarajus.md: -------------------------------------------------------------------------------- 1 | # Kosaraju's Strongly Connected Components 2 | 3 | Kosaraju's algorithm computes the Strongly Connected Components (SCCs) of a directed graph. An SCC is a subset of vertices 4 | in the graph for which every vertex is reachable from every other vertex in the subset, i.e. there exists a path between 5 | all pairs of vertices for the subset of vertices. 6 | 7 | Kosaraju's algorithm runs in `O(|V| + |E|)` for directed graphs, where `|V|` the number of vertices and `|E|` is the 8 | number of edges in the graph. So it runs in linear time. 9 | 10 | [wikipedia](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm) 11 | 12 | ## Syntax 13 | 14 | ```cpp 15 | template 16 | sccs_t kosarajus_strongly_connected_components(const directed_graph& graph); 17 | ``` 18 | 19 | - **graph** The graph for which to compute SCCs. 20 | - **return** A type consisting of a vector of vectors representing SCCs. -------------------------------------------------------------------------------- /docs/docs/algorithms/strongly-connected-components/tarjan.md: -------------------------------------------------------------------------------- 1 | # Tarjan's Strongly Connected Components 2 | 3 | Tarjan's algorithm computes the Strongly Connected Components (SCCs) of a directed graph. An SCC is a subset of vertices 4 | in the graph for which every vertex is reachable from every other vertex in the subset, i.e. there exists a path between 5 | all pairs of vertices for the subset of vertices. 6 | 7 | Tarjan's algorithm runs in `O(|V| + |E|)` for directed graphs, where `|V|` the number of vertices and `|E|` is the 8 | number of edges in the graph. So it runs in linear time. 9 | 10 | [wikipedia](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) 11 | 12 | ## Syntax 13 | 14 | ```cpp 15 | template 16 | [[nodiscard]] sccs_t tarjans_strongly_connected_components(const graph& graph); 17 | ``` 18 | 19 | - **graph** The graph for which to compute SCCs. 20 | - **return** A type consisting of a vector of vectors representing SCCs. -------------------------------------------------------------------------------- /docs/docs/algorithms/topological-sort/topological-sort.md: -------------------------------------------------------------------------------- 1 | # Topological sort algorithm 2 | 3 | Topological sort algorithm processing DAG(directed acyclic graph) using DFS traversal. 4 | Each vertex is visited only after all its dependencies are visited. 5 | The runtime of the algorithm is `O(|V|+|E|)` and the memory consumption is `O(|V|)`. 6 | 7 | [wikipedia](https://en.wikipedia.org/wiki/Topological_sorting) 8 | 9 | ## Syntax 10 | 11 | ```cpp 12 | template 13 | [[nodiscard]] std::optional> topological_sort( 14 | const graph& graph); 15 | ``` 16 | 17 | - **graph** The directed graph to traverse. 18 | - **return** Vector of vertices sorted in topological order. If the graph contains cycles, it returns std::nullopt. 19 | -------------------------------------------------------------------------------- /docs/docs/algorithms/traversal/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Traversal Algorithms", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/examples/example-basics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Basic Examples", 3 | "link": { 4 | "type": "generated-index" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/examples/example-basics/dot-serialization.md: -------------------------------------------------------------------------------- 1 | # Dot Serialization Example 2 | 3 | The `to_dot` function as defined under `graaf::io` can be used to searialize graphs to 4 | the [dot format](https://graphviz.org/doc/info/lang.html). This can be handy for debugging purposes, as well as for 5 | post-processing of your graphs in another tool which supports the format. 6 | 7 | ## Numeric primitive types 8 | 9 | Default vertex and edge writers are provided such that you can serialize graphs with numeric primitive vertices and 10 | edges. For instance: 11 | 12 | ```c++ 13 | graaf::undirected_graph my_graph{}; 14 | 15 | // ... 16 | 17 | graaf::io::to_dot(my_graph, path): 18 | ``` 19 | 20 | ## User defined types 21 | 22 | For user defined vertex and edge types, it is necessary to provide your own vertex and edge writers. These writers 23 | should take a vertex or edge as a parameter and serialize it to a string. This resulting string is used in the 24 | dot [attribute list](https://graphviz.org/doc/info/attrs.html) of the respective vertex or edge. 25 | 26 | For example, consider the following user defined vertex and edge types: 27 | 28 | ```c++ 29 | struct my_vertex { 30 | int number{}; 31 | std::string name{}; 32 | }; 33 | 34 | enum class edge_priority { LOW, HIGH }; 35 | 36 | struct my_edge { 37 | edge_priority priority{edge_priority::LOW}; 38 | float weight{}; 39 | }; 40 | ``` 41 | 42 | We define two lambdas to serialize these vertices and edges. Here we can use any of 43 | the [graphviz attributes](https://graphviz.org/doc/info/attrs.html). In this example, we 44 | use [fmtlib](https://github.com/fmtlib/fmt) to format our strings. 45 | 46 | **Vertex writer** 47 | 48 | ```c++ 49 | const auto vertex_writer{[](graaf::vertex_id_t vertex_id, 50 | const my_vertex& vertex) -> std::string { 51 | const auto color{vertex.number <= 25 ? "lightcyan" : "mediumspringgreen"}; 52 | return fmt::format("label=\"{}: {}\", fillcolor={}, style=filled", vertex_id, vertex.name, color); 53 | }}; 54 | ``` 55 | 56 | **Edge writer** 57 | 58 | ```c++ 59 | const auto edge_writer{[](const graaf::vertex_ids_t& /*edge_id*/, 60 | const my_edge& edge) -> std::string { 61 | const auto style{edge.priority == edge_priority::HIGH ? "solid" : "dashed"}; 62 | return fmt::format("label=\"{}\", style={}, color=gray, fontcolor=gray", edge.weight, style); 63 | }}; 64 | ``` 65 | 66 | Now let's create a directed graph and serialize it to dot: 67 | 68 | ```c++ 69 | graaf::directed_graph graph{}; 70 | 71 | const auto vertex_1{graph.add_vertex({10, "some data"})}; 72 | const auto vertex_2{graph.add_vertex({20, "some more data"})}; 73 | // ... 74 | 75 | graph.add_edge(vertex_1, vertex_2, {edge_priority::HIGH, 3.3}); 76 | // ... 77 | 78 | const std::filesystem::path dof_file_path{"./my_graph.dot"}; 79 | graaf::io::to_dot(my_graph, dof_file_path, vertex_writer, edge_writer); 80 | ``` 81 | 82 | The contents of `my_graph.dot` can be processed in any tool which supports dot format. For example, you can use 83 | the `dot` command line tool to generate png images: 84 | 85 | ```bash 86 | dot -Tpng ./my_graph.dot -o my_graph.png 87 | ``` 88 | 89 | Alternatively, you can 90 | use [graphviz online](https://dreampuf.github.io/GraphvizOnline/#digraph%20G%20%7B%0A%0A%20%20subgraph%20cluster_0%20%7B%0A%20%20%20%20style%3Dfilled%3B%0A%20%20%20%20color%3Dlightgrey%3B%0A%20%20%20%20node%20%5Bstyle%3Dfilled%2Ccolor%3Dwhite%5D%3B%0A%20%20%20%20a0%20-%3E%20a1%20-%3E%20a2%20-%3E%20a3%3B%0A%20%20%20%20label%20%3D%20%22process%20%231%22%3B%0A%20%20%7D%0A%0A%20%20subgraph%20cluster_1%20%7B%0A%20%20%20%20node%20%5Bstyle%3Dfilled%5D%3B%0A%20%20%20%20b0%20-%3E%20b1%20-%3E%20b2%20-%3E%20b3%3B%0A%20%20%20%20label%20%3D%20%22process%20%232%22%3B%0A%20%20%20%20color%3Dblue%0A%20%20%7D%0A%20%20start%20-%3E%20a0%3B%0A%20%20start%20-%3E%20b0%3B%0A%20%20a1%20-%3E%20b3%3B%0A%20%20b2%20-%3E%20a3%3B%0A%20%20a3%20-%3E%20a0%3B%0A%20%20a3%20-%3E%20end%3B%0A%20%20b3%20-%3E%20end%3B%0A%0A%20%20start%20%5Bshape%3DMdiamond%5D%3B%0A%20%20end%20%5Bshape%3DMsquare%5D%3B%0A%7D) 91 | for easy visualization: 92 | 93 |
94 | 

95 | 96 |

97 |
-------------------------------------------------------------------------------- /docs/docs/examples/example-basics/shortest-path.md: -------------------------------------------------------------------------------- 1 | # Shortest Path Example 2 | 3 | The shortest path algorithm implemented in `graaf::algorithm::get_shortest_path` can be used to compute the shortest 4 | path between any two vertices in a graph. 5 | 6 | Consider the following graph: 7 | 8 |
 9 | 

10 | 11 |

12 |
13 | 14 | In order to compute the shortest path between *vertex 0* and *vertex 2*, we call: 15 | 16 | ```c++ 17 | const auto maybe_shortest_path{bfs_shortest_path(graph, start, target)}; 18 | 19 | // Assert that we found a path at all 20 | assert(maybe_shortest_path.has_value()); 21 | auto shortest_path{maybe_shortest_path.value()}; 22 | ``` 23 | 24 | ## Visualizing the shortest path 25 | 26 | If we want to visualize the shortest path on the graph, we can create our own vertex and edge writers. These writers 27 | then determine the vertex and edge attributes based on whether the vertex or edge is contained in the shortest path. 28 | 29 | First, we create a datastructure of all edges on the shortest path such that we can query it in the edge writer: 30 | 31 | ```c++ 32 | // We use a set here for O(1) time contains checks 33 | std::unordered_set edges_on_shortest_path{}; 34 | 35 | // Convert the list of vertices on the shortest path to edges 36 | graaf::vertex_id_t prev{shortest_path.vertices.front()}; 37 | shortest_path.vertices.pop_front(); 38 | for (const auto current : shortest_path.vertices) { 39 | edges_on_shortest_path.insert(std::make_pair(prev, current)); 40 | prev = current; 41 | } 42 | ``` 43 | 44 | Now we can specify our custom writers: 45 | 46 | ```c++ 47 | const auto vertex_writer{ 48 | [start, target](graaf::vertex_id_t vertex_id, int vertex) -> std::string { 49 | if (vertex_id == start) { 50 | return "label=start"; 51 | } else if (vertex_id == target) { 52 | return "label=target"; 53 | } 54 | return "label=\"\""; 55 | }}; 56 | 57 | const auto edge_writer{ 58 | [&edges_on_shortest_path](const graaf::vertex_ids_t& edge_id, int edge) -> std::string { 59 | if (edges_on_shortest_path.contains(edge_id)) { 60 | return "label=\"\", color=red"; 61 | } 62 | return "label=\"\", color=gray, style=dashed"; 63 | }}; 64 | ``` 65 | 66 | This yields us the following visualization: 67 | 68 |
69 | 

70 | 71 |

72 |
-------------------------------------------------------------------------------- /docs/docs/examples/example-basics/transport-example.md: -------------------------------------------------------------------------------- 1 | # Network Example 2 | 3 | This example showcases graph traversal and shortest path algorithms in an undirected graph network. As such, it 4 | demonstrates the usage of the following classes and algorithms: 5 | 6 | - The undirected_graph implemented in `graaf::undirected_graph` 7 | - The shortest path algorithm implemented in `graaf::algorithm::get_shortest_path` 8 | - The graph traversal implemented in `graaf::algorithm::graph_traversal` 9 | 10 | Using the following graph: 11 | 12 |
 13 | 

14 | 15 |

16 |
17 | 18 | Custom vertex and edge. In order to use Dijkstra, we should provide the get_weight() function for the edge. 19 | 20 | ```cpp 21 | struct station { 22 | std::string name{}; 23 | }; 24 | 25 | struct railroad : public graaf::weighted_edge { 26 | double kilometers{}; 27 | [[nodiscard]] double get_weight() const noexcept override { 28 | return kilometers; 29 | } 30 | railroad(double distance) : kilometers(distance) {} 31 | ~railroad() {} 32 | }; 33 | ``` 34 | 35 | # Initializing graph, start and end vertecies 36 | 37 | First, we create data structure and initializing graph with vertices and edges 38 | 39 | ```cpp 40 | struct graph_with_start_and_target { 41 | graaf::undirected_graph graph{}; 42 | graaf::vertex_id_t start{}; 43 | graaf::vertex_id_t target{}; 44 | }; 45 | 46 | graph_with_start_and_target create_graph_with_start_and_target() { 47 | ... 48 | } 49 | ``` 50 | 51 | ## Visualizing graph traversal result 52 | 53 | For shortest path, colouring edges with red to indicate the shortest path for both weighted and unweighted graph 54 | We need to specify the start and end vertices in order to find the shortest path between the start and end vertices. 55 | 56 | Result of unweighted shortest path, chosen edges are coloured red 57 |
 58 | 

59 | 60 |

61 |
62 | 63 | Result of weighted shortest path, chosen edges are coloured red 64 |
 65 | 

66 | 67 |

68 |
69 | 70 | ```cpp 71 | void print_shortest_path(const graaf::undirected_graph& graph, 72 | const std::optional>& path, const std::string & filepath) { 73 | ... 74 | } 75 | 76 | void print_visited_vertices(const graaf::undirected_graph& graph, 77 | seen_vertices_t& seen, 78 | const std::string& filepath) { 79 | ... 80 | } 81 | ``` 82 | 83 | Creating an edge callback structure that will be passed as an argument in the graph traverse function 84 | The function is needed in order to be called inside the traverse function; see graph.tpp for context. 85 | 86 | ```cpp 87 | using seen_edges_t = std::unordered_set; 88 | struct record_edges_callback { 89 | seen_edges_t& seen_edges; 90 | 91 | record_edges_callback(seen_edges_t& seen_edges) 92 | : seen_edges{seen_edges} {} 93 | 94 | void operator()(const graaf::edge_id_t& edge) const { 95 | seen_edges.insert(edge); 96 | } 97 | }; 98 | ``` 99 | 100 | Result of shortest path BFS, visited edges are coloured red 101 |
102 | 

103 | 104 |

105 |
106 | 107 | ### Graph example usage 108 | 109 | First code block: traversing a weighted graph for the shortest path (Dijkstra) and printing the result to *.dot file. 110 | Second code block: traversing an unweighted graph for the shortest path and printing the result to *.dot file. 111 | The last one is traversing the graph from a given vertex and printing the result to *.dot file. 112 | 113 | ```cpp 114 | const auto [graph, start, target]{create_graph_with_start_and_target()}; 115 | 116 | const auto weighted_shortest_path{ 117 | graaf::algorithm::dijkstra_shortest_path(graph, start, target)}; 118 | print_shortest_path(graph, weighted_shortest_path, 119 | "example_weighted_graph.dot"); 120 | 121 | const auto unweighted_shortest_path{ 122 | graaf::algorithm::bfs_shortest_path(graph, start, target)}; 123 | print_shortest_path(graph, unweighted_shortest_path, 124 | "example_unwieghted_graph.dot"); 125 | 126 | seen_edges_t seen_edges{}; 127 | graaf::algorithm::breadth_first_traverse( 128 | graph, start, record_edges_callback{seen_edges}); 129 | print_visited_vertices(graph, seen_edges, 130 | "example_traverse_BFS_graph.dot"); 131 | ``` 132 | -------------------------------------------------------------------------------- /docs/docs/examples/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Examples 6 | 7 | This section contains example usages of the Graaf library. 8 | If there is a usecase you would like to see an example of, please open an issue in 9 | our [issue tracker](https://github.com/bobluppes/graaf/issues). -------------------------------------------------------------------------------- /docs/docs/quickstart/basics/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Quickstart - Basics", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "5 minutes to learn the most important Graaf concepts." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/quickstart/basics/architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Graaf Architecture 6 | 7 | From a very high level, the project is structured in two parts: 8 | 9 | - The graph classes and core data structures 10 | - Algorithms and additional functionality 11 | 12 | ## Graph classes and core data structures 13 | 14 | The main class of the library is the abstract graph class: 15 | 16 | ```c++ 17 | enum class edge_type { WEIGHTED, UNWEIGHTED }; 18 | enum class graph_spec { DIRECTED, UNDIRECTED }; 19 | 20 | template 21 | class graph {...}; 22 | ``` 23 | 24 | An instance of a `graph` can have user provided types for the vertices and edges. Internally, it stores the graph in an 25 | adjacency list, and has separate containers for the vertex and edge instances: 26 | 27 | ```c++ 28 | // N.B. These types are a bit more abstracted in the codebase behind using 29 | // declarations, but for clarity I have left this out. 30 | 31 | // Adjacency information is stored in a set for fast existence checks and fast removal 32 | std::unordered_map> adjacency_list_{}; 33 | 34 | // Storing these in a separate container has the advantage that 35 | // vertices and edges are only in memory once 36 | std::unordered_map vertices_{}; 37 | std::unordered_map, edge_t, edge_id_hash> edges_{}; 38 | ``` 39 | 40 | The `graph` class is abstract as it contains pure virtual private methods related to the handling of 41 | edges (`do_has_edge`, `do_get_edge`, `do_add_edge`, and `do_remove_edge`). 42 | 43 | ### Directed and undirected graphs 44 | 45 | There are two classes which publicly derive from `graph`: 46 | 47 | - `directed_graph` 48 | - `undirected_graph` 49 | 50 | ```c++ 51 | template 52 | class directed_graph final 53 | : public graph 54 | {...}; 55 | 56 | template 57 | class undirected_graph final 58 | : public graph 59 | {...}; 60 | ``` 61 | 62 | These are the classes which the user instantiates. 63 | 64 | They provide implementations for the pure virtual methods related to handling edges. The `unweighted_graph` first sorts 65 | the pair of vertex ids related to an edge before interacting with the internal `edges_` data structure. This ensures 66 | that an edge **a**->**b** is the same as an edge from **b**->**a**. 67 | 68 | ### Weighted graphs 69 | 70 | Certain algorithms (such as A*) operate on weighted graphs. A graph is automatically weighted if a primitive numeric 71 | type is passed as a template parameter to `EDGE_T`. Alternatively, user provided edge classes can publicly derive 72 | from `weighted_edge`. 73 | 74 | The `weighted_edge` class provides a default implementation for the `get_weight` method, but this can be overridden in 75 | the derived class: 76 | 77 | ```c++ 78 | template 79 | class weighted_edge { 80 | public: 81 | using weight_t = WEIGHT_T; 82 | /** 83 | * By default an edge has a unit weight. 84 | */ 85 | [[nodiscard]] virtual WEIGHT_T get_weight() const noexcept { return 1; }; 86 | }; 87 | 88 | ``` 89 | 90 | To create an unweighted graph, simply do not derive from `weighted_edge` in your edge class. 91 | 92 | ## Algorithms and additional functionality 93 | 94 | The idea here is to keep the graph classes as general-purpose as possible, and to not include use case specific logic ( 95 | such as dot serialization) as member functions. Therefore, each algorithm/utility function is implemented as a free 96 | function. -------------------------------------------------------------------------------- /docs/docs/quickstart/basics/creating-your-first-graph.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Creating Your First Graph 6 | 7 | 1. In your `main.cpp` import Graaf: 8 | 9 | ```c++ 10 | #include 11 | ``` 12 | 13 | 2. Define a directed graph `g` 14 | 15 | ```c++ 16 | graaf::directed_graph g; 17 | ``` 18 | 19 | 3. Add vertices to the graph: 20 | 21 | ```c++ 22 | const auto a = g.add_vertex('a'); 23 | const auto b = g.add_vertex('b'); 24 | const auto c = g.add_vertex('c'); 25 | ``` 26 | 27 | 4. Connect the vertices with edges: 28 | 29 | ```c++ 30 | g.add_edge(a, b, 1); 31 | g.add_edge(a, c, 1); 32 | ``` 33 | 34 | 5. Putting it all together: 35 | 36 | ```c++ 37 | #include 38 | 39 | int main() 40 | { 41 | graaf::directed_graph g; 42 | 43 | const auto a = g.add_vertex('a'); 44 | const auto b = g.add_vertex('b'); 45 | const auto c = g.add_vertex('c'); 46 | 47 | g.add_edge(a, b, 1); 48 | g.add_edge(a, c, 1); 49 | 50 | return 0; 51 | } 52 | ``` 53 | 54 | ### Congratulations! You just created the following graph 🎉 55 | 56 | ![Directed graph example](../../../static/img/quickstart/Graph.png) 57 | -------------------------------------------------------------------------------- /docs/docs/quickstart/basics/using-algorithms.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Using Algorithms 6 | 7 | 1. In your `main.cpp` import Graaf and algorithm of choice: 8 | 9 | ```c++ 10 | #include 11 | #include 12 | ``` 13 | 14 | 2. Build your graph: 15 | 16 | ```c++ 17 | graaf::directed_graph g; 18 | 19 | const auto a = g.add_vertex('a'); 20 | const auto b = g.add_vertex('b'); 21 | const auto c = g.add_vertex('c'); 22 | 23 | g.add_edge(a, b, 1); 24 | g.add_edge(c, a, 1); 25 | ``` 26 | 27 | 3. Run the algorithm: 28 | 29 | ```c++ 30 | std::cout << "Has cycles: " << graaf::algorithm::dfs_cycle_detection(g) << "\n"; 31 | ``` 32 | 33 | 4. Visualize the graph: 34 | 35 | ```c++ 36 | #include 37 | ... 38 | graaf::io::to_dot(g, "./Cycles.dot"); 39 | ``` 40 | 41 | 5. Putting it all together: 42 | 43 | ```c++ 44 | #include 45 | #include 46 | #include 47 | 48 | #include 49 | 50 | int main(int argc, char** argv) 51 | { 52 | graaf::directed_graph g; 53 | 54 | const auto a = g.add_vertex('a'); 55 | const auto b = g.add_vertex('b'); 56 | const auto c = g.add_vertex('c'); 57 | 58 | g.add_edge(a, b, 1); 59 | g.add_edge(c, a, 1); 60 | 61 | std::cout << "Vertices: " << g.vertex_count() << "\n"; 62 | std::cout << "Edges: " << g.edge_count() << "\n"; 63 | 64 | std::cout << "Has cycles: " << graaf::algorithm::dfs_cycle_detection(g) << "\n"; 65 | 66 | g.add_edge(b, c, 1); 67 | std::cout << "Has cycles: " << graaf::algorithm::dfs_cycle_detection(g) << "\n"; 68 | 69 | graaf::io::to_dot(g, "./Cycles.dot"); 70 | std::cout << "Run: dot -Tpng -o Cycles.png Cycles.dot\n"; 71 | 72 | return 0; 73 | } 74 | 75 | ``` 76 | 77 | ### Congratulations! You just detected if there are cycles in the following graph 78 | 79 | ![Cycle detection example](../../../static/img/quickstart/Cycles.png) 80 | -------------------------------------------------------------------------------- /docs/docs/quickstart/installation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Quickstart - Installation", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "5 minutes to learn the most important Graaf concepts." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/quickstart/installation/alternative-methods.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Alternative Installation Methods 6 | 7 | ## As a submodule 8 | 9 | Graaf can also be installed as a submodule in your project 10 | 11 | 1. Go to your project directory `cd projectdir` 12 | 1. Add Graaf as submodule `git submodule add https://github.com/bobluppes/graaf.git` 13 | 1. Then add Graaf as include directory with CMake: 14 | 15 | ```CMake 16 | include_directories("graaf/include") 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/docs/quickstart/installation/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Installation 6 | 7 | ## Graaf Header-Only Installation 8 | 9 | Installing Graaf on your project is easy! Simply copy the `graaflib` directory to your project and add it to your 10 | include path. 11 | 12 | 1. Copy `graaflib` to your project. 13 | 2. Before compiling, add the directory to your include path. 14 | 15 | ```bash 16 | # For C compiler 17 | export C_INCLUDE_PATH="/full/path/to/include/:$C_INCLUDE_PATH" 18 | # For Cpp compiler 19 | export CPLUS_INCLUDE_PATH="/full/path/to/include/:$CPLUS_INCLUDE_PATH" 20 | ``` 21 | 22 | Or in CMake: 23 | 24 | ```CMake 25 | include_directories("graaf/include") 26 | ``` 27 | 28 | 3. Include the graaf header in your sources. 29 | ```c++ 30 | #include 31 | 32 | ## CMake FetchContent 33 | 34 | Alternatively, this project can be pulled in using CMake's `FetchContent`: 35 | 36 | ```CMake 37 | include(FetchContent) 38 | FetchContent_Declare( 39 | graaflib 40 | GIT_REPOSITORY https://github.com/bobluppes/graaf.git 41 | GIT_TAG main 42 | ) 43 | FetchContent_MakeAvailable(graaflib) 44 | ``` 45 | 46 | Now you can link your target against `Graaf::Graaf`: 47 | 48 | ```CMake 49 | target_link_libraries(${PROJECT_NAME} PRIVATE Graaf::Graaf) 50 | ``` 51 | 52 | ## CMake Options 53 | 54 | There are multiple CMake Options available to choose how you want to build Graaf in your Project. 55 | 56 | - `SKIP_TESTS` 57 | - Default: `OFF` 58 | - Enabling skips building the tests. 59 | - `SKIP_EXAMPLES` 60 | - Default: `OFF` 61 | - This skips building the example usages of the Library. 62 | - `SKIP_BENCHMARKS` 63 | - Default: `OFF` 64 | - This skips building the Benchmarks. 65 | 66 | These Options can be set while executing the cmake command 67 | ```bash 68 | cmake -DSKIP_TESTS=ON -DSKIP_EXAMPLES=ON -DSKIP_BENCHMARKS=ON [source_directory] 69 | ``` 70 | 71 | or by setting them in your Projects CMakeLists.txt (before `FetchContent_MakeAvailable(graaflib)`) 72 | 73 | ```cmake 74 | set(SKIP_TESTS ON) 75 | set(SKIP_BENCHMARKS ON) 76 | set(SKIP_EXAMPLES ON) 77 | FetchContent_MakeAvailable(graaflib) 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/docs/quickstart/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Quickstart Intro 6 | 7 | Let's install Graaf in your project... 8 | 9 | ## Getting Started 10 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const {themes} = require('prism-react-renderer'); 5 | const lightCodeTheme = themes.github; 6 | const darkCodeTheme = themes.dracula; 7 | 8 | /** @type {import('@docusaurus/types').Config} */ 9 | const config = { 10 | title : 'Graaf lib', 11 | tagline : 'A general-purpose lightweight graph library implemented in C++', 12 | favicon : 'img/favicon.ico', 13 | 14 | // Set the production url of your site here 15 | url : 'https://bobluppes.github.io/', 16 | // Set the // pathname under which your site is served 17 | // For GitHub pages deployment, it is often '//' 18 | baseUrl : '/graaf/', 19 | 20 | // GitHub pages deployment config. 21 | // If you aren't using GitHub pages, you don't need these. 22 | organizationName : 'bobluppes', // Usually your GitHub org/user name. 23 | projectName : 'graaf', // Usually your repo name. 24 | 25 | trailingSlash : false, 26 | 27 | onBrokenLinks : 'throw', 28 | onBrokenMarkdownLinks : 'warn', 29 | 30 | // Even if you don't use internalization, you can use this field to set useful 31 | // metadata like html lang. For example, if your site is Chinese, you may want 32 | // to replace "en" with "zh-Hans". 33 | i18n : { 34 | defaultLocale : 'en', 35 | locales : ['en'], 36 | }, 37 | 38 | presets : [ 39 | [ 40 | 'classic', 41 | /** @type {import('@docusaurus/preset-classic').Options} */ 42 | ({ 43 | docs : { 44 | sidebarPath : require.resolve('./sidebars.js'), 45 | // Please change this to your repo. 46 | // Remove this to remove the "edit this page" links. 47 | editUrl : 'https://github.com/bobluppes/graaf/tree/main/', 48 | }, 49 | theme : { 50 | customCss : require.resolve('./src/css/custom.css'), 51 | }, 52 | }), 53 | ], 54 | ], 55 | 56 | themeConfig : 57 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 58 | ({ 59 | // Replace with your project's social card 60 | image : 'img/docusaurus-social-card.jpg', 61 | navbar : { 62 | title : 'Graaf lib', 63 | logo : { 64 | alt : 'Graaf Logo', 65 | src : 'img/graaf.png', 66 | }, 67 | items : [ 68 | { 69 | type : 'docSidebar', 70 | sidebarId : 'quickstartSidebar', 71 | position : 'left', 72 | label : 'Quickstart', 73 | }, 74 | { 75 | type : 'docSidebar', 76 | sidebarId : 'algorithmSidebar', 77 | position : 'left', 78 | label : 'Algorithms', 79 | }, 80 | { 81 | type : 'docSidebar', 82 | sidebarId : 'exampleSidebar', 83 | position : 'left', 84 | label : 'Examples', 85 | }, 86 | { 87 | href : 'https://github.com/bobluppes/graaf', 88 | label : 'GitHub', 89 | position : 'right', 90 | }, 91 | ], 92 | }, 93 | footer : { 94 | style : 'dark', 95 | links : [ 96 | { 97 | title : 'Docs', 98 | items : [ 99 | { 100 | label : 'Quickstart', 101 | to : '/docs/quickstart/intro', 102 | }, 103 | { 104 | label : 'Algorithms', 105 | to : '/docs/algorithms/intro', 106 | }, 107 | { 108 | label : 'Examples', 109 | to : '/docs/examples/intro', 110 | }, 111 | ], 112 | }, 113 | { 114 | title : 'Community', 115 | items : [ 116 | { 117 | label : 'Discord', 118 | href : 'https://discord.gg/cGczwRHJ9K', 119 | }, 120 | { 121 | label : 'Twitter', 122 | href : 'https://twitter.com/graaflib', 123 | }, 124 | ], 125 | }, 126 | { 127 | title : 'More', 128 | items : [ 129 | { 130 | label : 'GitHub', 131 | href : 'https://github.com/bobluppes/graaf', 132 | }, 133 | ], 134 | }, 135 | ], 136 | copyright : `Copyright © ${new Date().getFullYear()} Graaf, 137 | Inc.Built with Docusaurus.`, 138 | }, 139 | prism : { 140 | theme : lightCodeTheme, 141 | darkTheme : darkCodeTheme, 142 | }, 143 | }), 144 | }; 145 | 146 | module.exports = config; 147 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graaf", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "^3.5.2", 19 | "@docusaurus/preset-classic": "^3.7.0", 20 | "@mdx-js/react": "^3.1.0", 21 | "clsx": "^2.1.1", 22 | "prism-react-renderer": "^2.4.1", 23 | "react": "^18.0.0", 24 | "react-dom": "^18.0.0" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "^3.6.3", 28 | "@docusaurus/tsconfig": "^3.7.0", 29 | "@docusaurus/types": "^3.5.2", 30 | "@types/react": "^18.3.12", 31 | "typescript": "^5.8.2" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.5%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=18.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | quickstartSidebar : [ {type : 'autogenerated', dirName : 'quickstart'} ], 18 | algorithmSidebar : [ {type : 'autogenerated', dirName : 'algorithms'} ], 19 | exampleSidebar : [ {type : 'autogenerated', dirName : 'examples'} ], 20 | 21 | // But you can create a sidebar manually 22 | /* 23 | tutorialSidebar: [ 24 | 'intro', 25 | 'hello', 26 | { 27 | type: 'category', 28 | label: 'Tutorial', 29 | items: ['tutorial-basics/create-a-document'], 30 | }, 31 | ], 32 | */ 33 | }; 34 | 35 | module.exports = sidebars; 36 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Easy to Use', 14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 15 | description: ( 16 | <> 17 | Graaf is designed as a lightweight alternative for Boost Graph. The library 18 | is created to be easy to use right from the start. 19 | 20 | ), 21 | }, 22 | { 23 | title: 'General-Purpose', 24 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 25 | description: ( 26 | <> 27 | Graphs can wrap arbitrary types, i.e. graaf::directed_graph<MyVertexClass, MyEdgeClass> 28 | 29 | ), 30 | }, 31 | { 32 | title: 'Lightning Fast', 33 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 34 | description: ( 35 | <> 36 | Graaf is written in C++ with performance in mind. This allows users to 37 | efficiently perform complex algorithms on large graphs. 38 | 39 | ), 40 | }, 41 | ]; 42 | 43 | function Feature({title, Svg, description}: FeatureItem) { 44 | return ( 45 |
46 |
47 | 48 |
49 |
50 |

{title}

51 |

{description}

52 |
53 |
54 | ); 55 | } 56 | 57 | export default function HomepageFeatures(): JSX.Element { 58 | return ( 59 |
60 |
61 |
62 | {FeatureList.map((props, idx) => ( 63 | 64 | ))} 65 |
66 |
67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 21 | Graaf Quickstart - 5min ⏱️ 22 | 23 |
24 |
25 |
26 | ); 27 | } 28 | 29 | export default function Home(): JSX.Element { 30 | const {siteConfig} = useDocusaurusContext(); 31 | return ( 32 | 35 | 36 |
37 | 38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/examples/dot-serialization-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/dot-serialization-graph.png -------------------------------------------------------------------------------- /docs/static/img/examples/example_shortest_path_unweighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/example_shortest_path_unweighted.png -------------------------------------------------------------------------------- /docs/static/img/examples/example_shortest_path_weighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/example_shortest_path_weighted.png -------------------------------------------------------------------------------- /docs/static/img/examples/example_traversed_graph_BFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/example_traversed_graph_BFS.png -------------------------------------------------------------------------------- /docs/static/img/examples/example_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/example_usage.png -------------------------------------------------------------------------------- /docs/static/img/examples/shortest-path-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/shortest-path-graph.png -------------------------------------------------------------------------------- /docs/static/img/examples/shortest_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/examples/shortest_path.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/graaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/graaf.png -------------------------------------------------------------------------------- /docs/static/img/graph_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/graph_example.png -------------------------------------------------------------------------------- /docs/static/img/hacktoberfest-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/hacktoberfest-logo.png -------------------------------------------------------------------------------- /docs/static/img/jetbrains-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/quickstart/Cycles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/quickstart/Cycles.png -------------------------------------------------------------------------------- /docs/static/img/quickstart/Graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobluppes/graaf/e0ec371ae8266fb72d986c0de5c47286f34a0589/docs/static/img/quickstart/Graph.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Include fmt/fmtlib as a dependency 2 | include(FetchContent) 3 | FetchContent_Declare( 4 | fmt 5 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 6 | GIT_TAG 9.1.0 7 | ) 8 | FetchContent_MakeAvailable(fmt) 9 | 10 | file(GLOB EXAMPLES "**/main.cpp") 11 | 12 | foreach (EXAMPLE ${EXAMPLES}) 13 | get_filename_component(EXAMPLE_PATH ${EXAMPLE} DIRECTORY) 14 | get_filename_component(EXAMPLE_NAME ${EXAMPLE_PATH} NAME) 15 | 16 | # Create one executable per example directory 17 | add_executable( 18 | ${PROJECT_NAME}_${EXAMPLE_NAME}_example 19 | ${EXAMPLE} 20 | ) 21 | 22 | # Include src such that we can #include in the sources 23 | target_include_directories(${PROJECT_NAME}_${EXAMPLE_NAME}_example PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include) 24 | 25 | target_link_libraries( 26 | ${PROJECT_NAME}_${EXAMPLE_NAME}_example 27 | PRIVATE 28 | fmt::fmt 29 | ) 30 | endforeach() -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | This section contains example usages of the Graaf library. Each subdirectory contains sources for a separate executable. More details on each example can be found in the [documentation](https://bobluppes.github.io/graaf/docs/examples/Intro). 3 | 4 | If there is a usecase you would like to see an example of, please open an issue in our [issue tracker](https://github.com/bobluppes/graaf/issues). -------------------------------------------------------------------------------- /examples/dot_serialization/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @brief A user defined vertex type. 11 | * 12 | * Each vertex holds some arbitrary data. 13 | */ 14 | struct my_vertex { 15 | int number{}; 16 | std::string name{}; 17 | }; 18 | 19 | enum class edge_priority { LOW, HIGH }; 20 | 21 | /** 22 | * @brief A user defined edge type. 23 | * 24 | * Each edge has a priority and a weight set. 25 | */ 26 | struct my_edge { 27 | edge_priority priority{edge_priority::LOW}; 28 | float weight{}; 29 | }; 30 | 31 | auto create_graph() { 32 | graaf::directed_graph graph{}; 33 | 34 | const auto vertex_1{graph.add_vertex(my_vertex{10, "some data"})}; 35 | const auto vertex_2{graph.add_vertex(my_vertex{20, "some more data"})}; 36 | const auto vertex_3{graph.add_vertex(my_vertex{30, "abc"})}; 37 | const auto vertex_4{graph.add_vertex(my_vertex{40, "123"})}; 38 | const auto vertex_5{graph.add_vertex(my_vertex{50, "xyz"})}; 39 | 40 | graph.add_edge(vertex_1, vertex_2, my_edge{edge_priority::HIGH, 3.3}); 41 | graph.add_edge(vertex_2, vertex_1, my_edge{edge_priority::HIGH, 5.0}); 42 | graph.add_edge(vertex_1, vertex_3, my_edge{edge_priority::HIGH, 1.0}); 43 | graph.add_edge(vertex_3, vertex_4, my_edge{edge_priority::LOW, 2.0}); 44 | graph.add_edge(vertex_3, vertex_5, my_edge{edge_priority::HIGH, 3.0}); 45 | graph.add_edge(vertex_2, vertex_5, my_edge{edge_priority::LOW, 42.0}); 46 | 47 | return graph; 48 | } 49 | 50 | int main() { 51 | const auto my_graph{create_graph()}; 52 | 53 | const auto vertex_writer{[](graaf::vertex_id_t vertex_id, 54 | const my_vertex& vertex) -> std::string { 55 | const auto color{vertex.number <= 25 ? "lightcyan" : "mediumspringgreen"}; 56 | return fmt::format("label=\"{}: {}\", fillcolor={}, style=filled", 57 | vertex_id, vertex.name, color); 58 | }}; 59 | 60 | const auto edge_writer{[](const graaf::edge_id_t& /*edge_id*/, 61 | const auto& edge) -> std::string { 62 | const auto style{edge.priority == edge_priority::HIGH ? "solid" : "dashed"}; 63 | return fmt::format("label=\"{}\", style={}, color=gray, fontcolor=gray", 64 | edge.weight, style); 65 | }}; 66 | 67 | const std::filesystem::path dof_file_path{"./dot_example.dot"}; 68 | graaf::io::to_dot(my_graph, dof_file_path, vertex_writer, edge_writer); 69 | } -------------------------------------------------------------------------------- /examples/shortest_path/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct graph_with_start_and_target { 13 | graaf::directed_graph graph{}; 14 | graaf::vertex_id_t start{}; 15 | graaf::vertex_id_t target{}; 16 | }; 17 | 18 | graph_with_start_and_target create_graph_with_start_and_target() { 19 | graaf::directed_graph graph{}; 20 | 21 | const auto vertex_1{graph.add_vertex(10)}; 22 | const auto vertex_2{graph.add_vertex(20)}; 23 | const auto vertex_3{graph.add_vertex(30)}; 24 | const auto vertex_4{graph.add_vertex(40)}; 25 | const auto vertex_5{graph.add_vertex(50)}; 26 | const auto vertex_6{graph.add_vertex(60)}; 27 | 28 | graph.add_edge(vertex_1, vertex_2, 100); 29 | graph.add_edge(vertex_3, vertex_2, 200); 30 | graph.add_edge(vertex_3, vertex_5, 300); 31 | graph.add_edge(vertex_2, vertex_4, 400); 32 | graph.add_edge(vertex_4, vertex_3, 500); 33 | graph.add_edge(vertex_4, vertex_6, 600); 34 | graph.add_edge(vertex_6, vertex_3, 700); 35 | 36 | return {graph, vertex_1, vertex_3}; 37 | } 38 | 39 | int main() { 40 | const auto [graph, start, target]{create_graph_with_start_and_target()}; 41 | 42 | const auto maybe_shortest_path{ 43 | graaf::algorithm::bfs_shortest_path(graph, start, target)}; 44 | assert(maybe_shortest_path.has_value()); 45 | auto shortest_path{maybe_shortest_path.value()}; 46 | 47 | std::unordered_set 48 | edges_on_shortest_path{}; 49 | 50 | graaf::vertex_id_t prev{shortest_path.vertices.front()}; 51 | shortest_path.vertices.pop_front(); 52 | for (const auto current : shortest_path.vertices) { 53 | edges_on_shortest_path.insert(std::make_pair(prev, current)); 54 | prev = current; 55 | } 56 | 57 | // TODO(bluppes): Directly capture local binding once we have support for it 58 | // in Clang-16 59 | const auto vertex_writer{ 60 | [start = start, target = target](graaf::vertex_id_t vertex_id, 61 | int vertex) -> std::string { 62 | if (vertex_id == start) { 63 | return "label=start, fillcolor=white, style=filled"; 64 | } else if (vertex_id == target) { 65 | return "label=target, fillcolor=white, style=filled"; 66 | } 67 | return "label=\"\", color=gray, fillcolor=white, style=filled"; 68 | }}; 69 | 70 | const auto edge_writer{ 71 | [&edges_on_shortest_path](const graaf::edge_id_t& edge_id, 72 | const auto& /*edge*/) -> std::string { 73 | if (edges_on_shortest_path.contains(edge_id)) { 74 | return "label=\"\", color=red"; 75 | } 76 | return "label=\"\", color=gray, style=dashed"; 77 | }}; 78 | 79 | const std::filesystem::path output{"shortest_path.dot"}; 80 | graaf::io::to_dot(graph, output, vertex_writer, edge_writer); 81 | } -------------------------------------------------------------------------------- /include/README.md: -------------------------------------------------------------------------------- 1 | # Graaf Header-Only Installation 2 | Installing Graaf on your project is easy! Simply copy the `graaflib` directory to your project and add it to your include path. For more details or alternative installation methods, take a look at our [installation guide](https://bobluppes.github.io/graaf/docs/quickstart/installation). 3 | 4 | 1. Copy `graaflip` to your project. 5 | 2. Before compiling, add the directory to your include path. 6 | ```bash 7 | # For C compiler 8 | export C_INCLUDE_PATH="/full/path/to/include/graaflib/:$C_INCLUDE_PATH" 9 | # For Cpp compiler 10 | export CPLUS_INCLUDE_PATH="/full/path/to/include/graaflib/:$CPLUS_INCLUDE_PATH" 11 | ``` 12 | 13 | Or in CMake: 14 | ```CMake 15 | include_directories("graaf/src/graaflib") 16 | ``` 17 | 3. Include the graaf header in your sources. 18 | ```c++ 19 | #include 20 | ``` -------------------------------------------------------------------------------- /include/graaflib/algorithm/clique_detection/bron_kerbosch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace graaf::algorithm { 6 | /** 7 | * @brief Finds all cliques in an undirected graph using the Bron-Kerbosch 8 | * algorithm. 9 | * 10 | * The Bron-Kerbosch algorithm is used for finding all cliques in an undirected 11 | * graph. A clique_detection is a subset of vertices such that every two 12 | * distinct vertices are adjacent. This function returns a list of all cliques, 13 | * each represented as a vector of vertex identifiers. 14 | * 15 | * @tparam V The vertex type of the graph. 16 | * @tparam E The edge type of the graph. 17 | * @param graph The graph in which we want to find the cliques. 18 | * @return A vector of cliques, each represented as a vector of vertex 19 | * identifiers. 20 | */ 21 | template 22 | std::vector> bron_kerbosch( 23 | const graph& graph); 24 | 25 | } // namespace graaf::algorithm 26 | 27 | #include "bron_kerbosch.tpp" 28 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/clique_detection/bron_kerbosch.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bron_kerbosch.h" 13 | 14 | namespace graaf::algorithm { 15 | 16 | namespace detail { 17 | using clique_collection_t = std::vector>; 18 | using vertex_set_t = std::unordered_set; 19 | 20 | vertex_set_t do_get_intersection(const vertex_set_t& lhs, 21 | const vertex_set_t& rhs) { 22 | vertex_set_t intersection{}; 23 | 24 | for (const auto& vertex : lhs) { 25 | if (rhs.contains(vertex)) { 26 | intersection.insert(vertex); 27 | } 28 | } 29 | 30 | return intersection; 31 | } 32 | 33 | vertex_set_t set_union(const vertex_set_t& lhs, const vertex_set_t& rhs) { 34 | vertex_set_t union_set = lhs; 35 | for (const auto& vertex : rhs) union_set.insert(vertex); 36 | 37 | return union_set; 38 | } 39 | 40 | vertex_set_t set_difference(const vertex_set_t& lhs, const vertex_set_t rhs) { 41 | vertex_set_t difference_set = lhs; 42 | 43 | for (const auto& vertex : rhs) { 44 | difference_set.erase(vertex); 45 | } 46 | 47 | return difference_set; 48 | } 49 | 50 | template 51 | void do_bron_kerbosch_maximal_clique( 52 | std::vector& clique, vertex_set_t& vertices, 53 | vertex_set_t& excluded_vertices, clique_collection_t& maximal_cliques, 54 | const graph& graph) { 55 | if (vertices.empty() && excluded_vertices.empty()) { 56 | maximal_cliques.push_back(clique); 57 | return; 58 | } 59 | 60 | // union_set = candidate_vertices ⋃ excluded_vertices 61 | auto union_set = set_union(vertices, excluded_vertices); 62 | 63 | const auto pivot_vertex = *std::ranges::max_element( 64 | union_set, {}, 65 | [&graph](vertex_id_t id) { return graph.get_neighbors(id).size(); }); 66 | 67 | // vertices_to_process = candidate_vertices \ N(pivot_vertex) 68 | auto vertices_to_process = 69 | set_difference(vertices, graph.get_neighbors(pivot_vertex)); 70 | 71 | for (const auto& vertex : vertices_to_process) { 72 | auto vertex_neighbors = graph.get_neighbors(vertex); 73 | 74 | // Intersection candidate_vertices ⋂ N(vertex) 75 | std::unordered_set vertices_intersection{}; 76 | vertices_intersection = do_get_intersection(vertices, vertex_neighbors); 77 | 78 | // Intersection excluded_vertices ⋂ N(vertex) 79 | std::unordered_set exclude_intersection{}; 80 | exclude_intersection = 81 | do_get_intersection(excluded_vertices, vertex_neighbors); 82 | 83 | clique.push_back(vertex); 84 | do_bron_kerbosch_maximal_clique(clique, vertices_intersection, 85 | exclude_intersection, maximal_cliques, 86 | graph); 87 | clique.pop_back(); 88 | 89 | vertices.erase(vertex); 90 | excluded_vertices.insert(vertex); 91 | } 92 | } 93 | }; // namespace detail 94 | 95 | template 96 | std::vector> bron_kerbosch( 97 | const graph& graph) { 98 | detail::clique_collection_t maximal_cliques{}; 99 | std::vector clique{}; 100 | detail::vertex_set_t vertices{}; 101 | detail::vertex_set_t excluded{}; 102 | 103 | for (const auto& [vertex_id, _] : graph.get_vertices()) { 104 | vertices.insert(vertex_id); 105 | } 106 | detail::do_bron_kerbosch_maximal_clique(clique, vertices, excluded, 107 | maximal_cliques, graph); 108 | 109 | return maximal_cliques; 110 | } 111 | 112 | }; // namespace graaf::algorithm 113 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/coloring/greedy_graph_coloring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace graaf::algorithm { 7 | 8 | /** 9 | * @brief Greedy Graph Coloring Algorithm 10 | * 11 | * This function performs greedy graph coloring on a given graph. It assigns 12 | * colors to vertices in such a way that no two adjacent vertices share the 13 | * same color. The algorithm is heuristic and does not guarantee an optimal 14 | * coloring. 15 | * 16 | * @tparam GRAPH The type of the graph 17 | * @param graph The graph object 18 | * @return An unordered_map where keys are vertex identifiers and values are 19 | * their respective colors. 20 | */ 21 | template 22 | std::unordered_map greedy_graph_coloring(const GRAPH& graph); 23 | 24 | } // namespace graaf::algorithm 25 | 26 | #include "greedy_graph_coloring.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/coloring/greedy_graph_coloring.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | 6 | #include "greedy_graph_coloring.h" 7 | 8 | namespace graaf::algorithm { 9 | 10 | template 11 | std::unordered_map greedy_graph_coloring(const GRAPH& graph) { 12 | // Initialize a map to store the coloring 13 | std::unordered_map coloring{}; 14 | 15 | // Get the vertices from the graph 16 | const auto& vertices = graph.get_vertices(); 17 | 18 | // Iterate through each vertex 19 | for (const auto& [current_vertex_id, _] : vertices) { 20 | // Iterate through neighboring vertices 21 | // Find the smallest available color for the current vertex 22 | int available_color{0}; 23 | for (const auto neighbor_id : graph.get_neighbors(current_vertex_id)) { 24 | if (coloring.contains(neighbor_id)) { 25 | const auto neighbor_color{coloring.at(neighbor_id)}; 26 | if (neighbor_color >= available_color) { 27 | available_color = neighbor_color + 1; 28 | } 29 | } 30 | } 31 | 32 | // Assign the color to the current vertex 33 | coloring[current_vertex_id] = available_color; 34 | } 35 | 36 | return coloring; 37 | } 38 | 39 | } // namespace graaf::algorithm 40 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/coloring/welsh_powell.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace graaf::algorithm { 6 | 7 | /** 8 | * @brief Applies the Welsh-Powell greedy graph coloring algorithm to the given 9 | * graph. 10 | * 11 | * The function sorts the vertices by their degree in descending order, then 12 | * attempts to color the graph in a way that no two adjacent vertices share the 13 | * same color. 14 | * 15 | * @tparam GRAPH The graph type. 16 | * @param graph The input graph to color. 17 | * @return std::unordered_map An unordered map from vertex ID 18 | * to color if the graph could be colored, or an empty map otherwise. 19 | */ 20 | template 21 | std::unordered_map welsh_powell_coloring(const GRAPH& graph); 22 | 23 | } // namespace graaf::algorithm 24 | 25 | #include "welsh_powell.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/coloring/welsh_powell.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "welsh_powell.h" 11 | 12 | namespace graaf::algorithm { 13 | 14 | template 15 | std::unordered_map welsh_powell_coloring(const GRAPH& graph) { 16 | using degree_vertex_pair = std::pair; 17 | 18 | // Step 1: Sort vertices by degree in descending order 19 | std::vector degree_vertex_pairs; 20 | for (const auto& [vertex_id, _] : graph.get_vertices()) { 21 | int degree = properties::vertex_degree(graph, vertex_id); 22 | degree_vertex_pairs.emplace_back(degree, vertex_id); 23 | } 24 | 25 | std::sort(degree_vertex_pairs.rbegin(), degree_vertex_pairs.rend()); 26 | 27 | // Step 2: Assign colors to vertices 28 | std::unordered_map color_map; 29 | 30 | for (const auto [_, current_vertex] : degree_vertex_pairs) { 31 | int color = 0; // Start with color 0 32 | 33 | // Check colors of adjacent vertices 34 | for (const auto& neighbor : graph.get_neighbors(current_vertex)) { 35 | // If neighbor is already colored with this color, increment the color 36 | if (color_map.contains(neighbor) && color_map[neighbor] == color) { 37 | color++; 38 | } 39 | } 40 | 41 | // Assign the color to the current vertex 42 | color_map[current_vertex] = color; 43 | } 44 | 45 | return color_map; 46 | } 47 | 48 | } // namespace graaf::algorithm 49 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/cycle_detection/dfs_cycle_detection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace graaf::algorithm { 6 | 7 | /* 8 | * @brief Traverses a directed graph and checks for cycles. 9 | * 10 | * @param graph The directed graph to traverse. 11 | */ 12 | template 13 | [[nodiscard]] bool dfs_cycle_detection( 14 | const graph &graph); 15 | 16 | /* 17 | * @brief Traverses an undirected graph and checks for cycles. 18 | * 19 | * @param graph The undirected graph to traverse. 20 | */ 21 | template 22 | [[nodiscard]] bool dfs_cycle_detection( 23 | const graph &graph); 24 | 25 | } // namespace graaf::algorithm 26 | 27 | #include "dfs_cycle_detection.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/cycle_detection/dfs_cycle_detection.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "dfs_cycle_detection.h" 8 | 9 | namespace graaf::algorithm { 10 | 11 | namespace detail { 12 | 13 | enum class vertex_color { UNVISITED, VISITED, NO_CYCLE }; 14 | 15 | template 16 | bool do_dfs_directed( 17 | const graph& graph, 18 | std::unordered_map& colored_vertices, 19 | vertex_id_t current) { 20 | colored_vertices[current] = vertex_color::VISITED; 21 | 22 | for (const auto& neighbour_vertex : graph.get_neighbors(current)) { 23 | if (colored_vertices[neighbour_vertex] == vertex_color::UNVISITED) { 24 | if (do_dfs_directed(graph, colored_vertices, neighbour_vertex)) 25 | return true; 26 | } else if (colored_vertices[neighbour_vertex] == vertex_color::VISITED) { 27 | return true; 28 | } 29 | } 30 | 31 | colored_vertices[current] = vertex_color::NO_CYCLE; 32 | return false; 33 | } 34 | 35 | template 36 | bool do_dfs_undirected( 37 | const graph& graph, 38 | std::unordered_map& visited_vertices, 39 | std::unordered_map& parent_vertices, 40 | vertex_id_t parent_vertex, vertex_id_t current) { 41 | visited_vertices[current] = true; 42 | 43 | for (const auto& neighbour_vertex : graph.get_neighbors(current)) { 44 | if (neighbour_vertex == parent_vertex) continue; 45 | 46 | if (visited_vertices[neighbour_vertex]) return true; 47 | 48 | parent_vertices[neighbour_vertex] = parent_vertex; 49 | 50 | if (do_dfs_undirected(graph, visited_vertices, parent_vertices, 51 | neighbour_vertex, 52 | parent_vertices[neighbour_vertex])) { 53 | return true; 54 | } 55 | } 56 | 57 | return false; 58 | } 59 | 60 | } // namespace detail 61 | 62 | template 63 | bool dfs_cycle_detection(const graph& graph) { 64 | std::unordered_map colored_vertices{}; 65 | 66 | for (const auto& vertex : graph.get_vertices()) { 67 | using enum detail::vertex_color; 68 | if (colored_vertices[vertex.first] == UNVISITED && 69 | detail::do_dfs_directed(graph, colored_vertices, vertex.first)) { 70 | return true; 71 | } 72 | } 73 | 74 | return false; 75 | } 76 | 77 | template 78 | bool dfs_cycle_detection(const graph& graph) { 79 | // Number of vertices cannot be zero (in case if graph is empty) 80 | if (graph.edge_count() >= graph.vertex_count() && graph.vertex_count() > 0) { 81 | return true; 82 | } 83 | 84 | std::unordered_map visited_vertices{}; 85 | std::unordered_map parent_vertices{}; 86 | 87 | for (const auto& vertex : graph.get_vertices()) { 88 | if (!visited_vertices.contains(vertex.first) && 89 | detail::do_dfs_undirected(graph, visited_vertices, parent_vertices, 90 | vertex.first, 91 | parent_vertices[vertex.first])) { 92 | return true; 93 | } 94 | } 95 | 96 | return false; 97 | } 98 | 99 | } // namespace graaf::algorithm 100 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/graph_traversal/breadth_first_search.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * @brief Traverses the graph, starting at start_vertex, and visits all 13 | * reachable vertices in a BFS manner. 14 | * 15 | * @param graph The graph to traverse. 16 | * @param start_vertex Vertex id where the traversal should be started. 17 | * @param edge_callback A callback which is called for each traversed edge. 18 | * Should be invocable with an edge_id_t. 19 | * @param search_termination_strategy A unary predicate to indicate whether we 20 | * should continue the traversal or not. Traversal continues while this 21 | * predicate returns false. 22 | */ 23 | template < 24 | typename V, typename E, graph_type T, 25 | typename EDGE_CALLBACK_T = detail::noop_callback, 26 | typename SEARCH_TERMINATION_STRATEGY_T = detail::exhaustive_search_strategy> 27 | requires std::invocable && 28 | std::is_invocable_r_v 30 | void breadth_first_traverse( 31 | const graph &graph, vertex_id_t start_vertex, 32 | const EDGE_CALLBACK_T &edge_callback, 33 | const SEARCH_TERMINATION_STRATEGY_T &search_termination_strategy = 34 | SEARCH_TERMINATION_STRATEGY_T{}); 35 | 36 | } // namespace graaf::algorithm 37 | 38 | #include "breadth_first_search.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/graph_traversal/breadth_first_search.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "breadth_first_search.h" 7 | 8 | namespace graaf::algorithm { 9 | 10 | template 12 | requires std::invocable && 13 | std::is_invocable_r_v 15 | void breadth_first_traverse( 16 | const graph& graph, vertex_id_t start_vertex, 17 | const EDGE_CALLBACK_T& edge_callback, 18 | const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) { 19 | std::unordered_set seen_vertices{}; 20 | std::queue to_explore{}; 21 | 22 | to_explore.push(start_vertex); 23 | 24 | while (!to_explore.empty()) { 25 | const auto current{to_explore.front()}; 26 | to_explore.pop(); 27 | 28 | if (search_termination_strategy(current)) { 29 | return; 30 | } 31 | 32 | seen_vertices.insert(current); 33 | for (const auto neighbor_vertex : graph.get_neighbors(current)) { 34 | if (!seen_vertices.contains(neighbor_vertex)) { 35 | edge_callback(edge_id_t{current, neighbor_vertex}); 36 | to_explore.push(neighbor_vertex); 37 | } 38 | } 39 | } 40 | } 41 | 42 | } // namespace graaf::algorithm 43 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/graph_traversal/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace graaf::algorithm { 6 | 7 | namespace detail { 8 | 9 | /** 10 | * An edge callback which does nothing. 11 | */ 12 | struct noop_callback { 13 | void operator()(const edge_id_t& /*edge*/) const {} 14 | }; 15 | 16 | /* 17 | * A unary predicate which always returns false, effectively resulting in an 18 | * exhaustive search. 19 | */ 20 | struct exhaustive_search_strategy { 21 | [[nodiscard]] bool operator()(const vertex_id_t /*vertex*/) const { 22 | return false; 23 | } 24 | }; 25 | 26 | } // namespace detail 27 | 28 | } // namespace graaf::algorithm -------------------------------------------------------------------------------- /include/graaflib/algorithm/graph_traversal/depth_first_search.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * @brief Traverses the graph, starting at start_vertex, and visits all 13 | * reachable vertices in a DFS manner. 14 | * 15 | * @param graph The graph to traverse. 16 | * @param start_vertex Vertex id where the traversal should be started. 17 | * @param edge_callback A callback which is called for each traversed edge. 18 | * Should be invocable with an edge_id_t. 19 | * @param search_termination_strategy A unary predicate to indicate whether we 20 | * should continue the traversal or not. Traversal continues while this 21 | * predicate returns false. 22 | */ 23 | template < 24 | typename V, typename E, graph_type T, 25 | typename EDGE_CALLBACK_T = detail::noop_callback, 26 | typename SEARCH_TERMINATION_STRATEGY_T = detail::exhaustive_search_strategy> 27 | requires std::invocable && 28 | std::is_invocable_r_v 30 | void depth_first_traverse( 31 | const graph &graph, vertex_id_t start_vertex, 32 | const EDGE_CALLBACK_T &edge_callback, 33 | const SEARCH_TERMINATION_STRATEGY_T &search_termination_strategy = 34 | SEARCH_TERMINATION_STRATEGY_T{}); 35 | 36 | } // namespace graaf::algorithm 37 | 38 | #include "depth_first_search.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/graph_traversal/depth_first_search.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "depth_first_search.h" 7 | 8 | namespace graaf::algorithm { 9 | 10 | namespace detail { 11 | 12 | template 14 | bool do_dfs(const graph& graph, 15 | std::unordered_set& seen_vertices, vertex_id_t current, 16 | const EDGE_CALLBACK_T& edge_callback, 17 | const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) { 18 | seen_vertices.insert(current); 19 | 20 | if (search_termination_strategy(current)) { 21 | return false; 22 | } 23 | 24 | for (auto neighbor_vertex : graph.get_neighbors(current)) { 25 | if (!seen_vertices.contains(neighbor_vertex)) { 26 | edge_callback(edge_id_t{current, neighbor_vertex}); 27 | if (!do_dfs(graph, seen_vertices, neighbor_vertex, edge_callback, 28 | search_termination_strategy)) { 29 | // Further down the call stack we have hit the search termination point. 30 | // Bubble this up the call stack. 31 | return false; 32 | } 33 | } 34 | } 35 | 36 | // We did not hit the search termination point 37 | return true; 38 | } 39 | 40 | } // namespace detail 41 | 42 | template 44 | requires std::invocable && 45 | std::is_invocable_r_v 47 | void depth_first_traverse( 48 | const graph& graph, vertex_id_t start_vertex, 49 | const EDGE_CALLBACK_T& edge_callback, 50 | const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) { 51 | std::unordered_set seen_vertices{}; 52 | detail::do_dfs(graph, seen_vertices, start_vertex, edge_callback, 53 | search_termination_strategy); 54 | } 55 | 56 | } // namespace graaf::algorithm 57 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/minimum_spanning_tree/kruskal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace graaf::algorithm { 9 | /** 10 | * Computes the minimum spanning tree (MST) or minimum spanning forest of a 11 | * graph using Kruskal's algorithm. 12 | * 13 | * @tparam V The vertex type of the graph. 14 | * @tparam E The edge type of the graph. 15 | * @param graph The input graph. 16 | * @return A vector of edges forming the MST or minimum spanning forest. 17 | */ 18 | template 19 | [[nodiscard]] std::vector kruskal_minimum_spanning_tree( 20 | const graph& graph); 21 | 22 | } // namespace graaf::algorithm 23 | 24 | #include "kruskal.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/minimum_spanning_tree/kruskal.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "kruskal.h" 9 | 10 | namespace graaf::algorithm { 11 | 12 | // Disjoint Set Union to maintain sets of vertices 13 | namespace detail { 14 | void do_make_set(vertex_id_t v, 15 | std::unordered_map& parent, 16 | std::unordered_map& rank) { 17 | parent[v] = v; 18 | rank[v] = 0; 19 | } 20 | 21 | vertex_id_t do_find_set(vertex_id_t vertex, 22 | std::unordered_map& parent) { 23 | if (vertex == parent[vertex]) { 24 | return vertex; 25 | } 26 | return parent[vertex] = do_find_set(parent[vertex], parent); 27 | } 28 | 29 | void do_merge_sets(vertex_id_t vertex_a, vertex_id_t vertex_b, 30 | std::unordered_map& parent, 31 | std::unordered_map& rank) { 32 | vertex_a = do_find_set(vertex_a, parent); 33 | vertex_b = do_find_set(vertex_b, parent); 34 | 35 | if (vertex_a != vertex_b) { 36 | if (rank[vertex_a] < rank[vertex_b]) { 37 | std::swap(vertex_a, vertex_b); 38 | } 39 | parent[vertex_b] = vertex_a; 40 | 41 | if (rank[vertex_a] == rank[vertex_b]) { 42 | ++rank[vertex_a]; 43 | } 44 | } 45 | } 46 | 47 | template 48 | struct edge_to_process { 49 | public: 50 | vertex_id_t vertex_a, vertex_b; 51 | T weight_; 52 | 53 | edge_to_process(vertex_id_t vertex_u, vertex_id_t vertex_w, T weight) 54 | : vertex_a{vertex_u}, vertex_b{vertex_w}, weight_{weight} {}; 55 | 56 | [[nodiscard]] bool operator<(const edge_to_process& other) const { 57 | // We order based on the weight first; breaking ties using the source/target 58 | // vertices 59 | if (weight_ != other.weight_) return weight_ < other.weight_; 60 | if (vertex_a != other.vertex_a) return vertex_a < other.vertex_a; 61 | return vertex_b < other.vertex_b; 62 | } 63 | }; 64 | 65 | }; // namespace detail 66 | 67 | template 68 | std::vector kruskal_minimum_spanning_tree( 69 | const graph& graph) { 70 | // unordered_map in case of deletion of vertices 71 | std::unordered_map rank, parent; 72 | std::vector> edges_to_process{}; 73 | std::vector mst_edges{}; 74 | 75 | for (const auto& vertex : graph.get_vertices()) { 76 | detail::do_make_set(vertex.first, parent, rank); 77 | } 78 | for (const auto& edge : graph.get_edges()) { 79 | edges_to_process.push_back( 80 | {edge.first.first, edge.first.second, edge.second}); 81 | } 82 | 83 | std::sort(edges_to_process.begin(), edges_to_process.end()); 84 | 85 | for (const auto& edge : edges_to_process) { 86 | if (detail::do_find_set(edge.vertex_a, parent) != 87 | detail::do_find_set(edge.vertex_b, parent)) { 88 | mst_edges.push_back({edge.vertex_a, edge.vertex_b}); 89 | detail::do_merge_sets(edge.vertex_a, edge.vertex_b, parent, rank); 90 | } 91 | // Found MST E == V - 1 92 | if (mst_edges.size() == graph.vertex_count() - 1) return mst_edges; 93 | } 94 | // Returns minimum spanning forest 95 | return mst_edges; 96 | } 97 | 98 | }; // namespace graaf::algorithm 99 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/minimum_spanning_tree/prim.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * Computes the minimum spanning tree (MST) of a graph using Prim's algorithm. 13 | * 14 | * @tparam V The vertex type of the graph. 15 | * @tparam E The edge type of the graph. 16 | * @param graph The input graph. Should be undirected. 17 | * @param start_vertex The starting vertex for the MST construction. 18 | * @return An optional containing a vector of edges forming the MST if it 19 | * exists, or an empty optional if the MST doesn't exist (e.g., graph is not 20 | * connected). 21 | */ 22 | template 23 | [[nodiscard]] std::optional > prim_minimum_spanning_tree( 24 | const graph& graph, vertex_id_t start_vertex); 25 | 26 | } // namespace graaf::algorithm 27 | 28 | #include "prim.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/minimum_spanning_tree/prim.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "prim.h" 9 | 10 | namespace graaf::algorithm { 11 | 12 | namespace detail { 13 | 14 | template 15 | [[nodiscard]] std::vector find_candidate_edges( 16 | const GRAPH_T& graph, 17 | const std::unordered_set& fringe_vertices) { 18 | std::vector candidates{}; 19 | 20 | for (const auto fringe_vertex : fringe_vertices) { 21 | for (const auto neighbor : graph.get_neighbors(fringe_vertex)) { 22 | if (!fringe_vertices.contains(neighbor)) { 23 | candidates.emplace_back(fringe_vertex, neighbor); 24 | } 25 | } 26 | } 27 | 28 | return candidates; 29 | } 30 | 31 | }; // namespace detail 32 | 33 | template 34 | std::optional> prim_minimum_spanning_tree( 35 | const graph& graph, 36 | vertex_id_t start_vertex) { 37 | std::vector edges_in_mst{}; 38 | edges_in_mst.reserve( 39 | graph.edge_count()); // Reserve the upper bound of edges in the mst 40 | 41 | std::unordered_set fringe_vertices{start_vertex}; 42 | 43 | while (fringe_vertices.size() < graph.vertex_count()) { 44 | const auto candidates{detail::find_candidate_edges(graph, fringe_vertices)}; 45 | 46 | if (candidates.empty()) { 47 | // The graph is not connected 48 | return std::nullopt; 49 | } 50 | 51 | const edge_id_t mst_edge{*std::ranges::min_element( 52 | candidates, 53 | [graph](const edge_id_t& lhs, const edge_id_t& rhs) -> bool { 54 | return get_weight(graph.get_edge(lhs)) < 55 | get_weight(graph.get_edge(rhs)); 56 | })}; 57 | 58 | edges_in_mst.emplace_back(mst_edge); 59 | fringe_vertices.insert(mst_edge.second); 60 | } 61 | 62 | return edges_in_mst; 63 | } 64 | 65 | }; // namespace graaf::algorithm 66 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/a_star.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace graaf::algorithm { 11 | 12 | /** 13 | * @brief Finds the shortest path between a start_vertex and target_vertex 14 | * using the A* search algorithm. 15 | * 16 | * @param graph The graph to search in. 17 | * @param start_vertex The starting vertex for the search. 18 | * @param target_vertex The target vertex to reach. 19 | * @param heuristic A heuristic function estimating the cost from a vertex to 20 | * the target. 21 | * @return An optional containing the shortest path if found, or std::nullopt if 22 | * no path exists. 23 | */ 24 | template ()))> 26 | requires std::is_invocable_r_v 27 | std::optional> a_star_search(const graph& graph, 28 | vertex_id_t start_vertex, 29 | vertex_id_t target_vertex, 30 | const HEURISTIC_T& heuristic); 31 | 32 | } // namespace graaf::algorithm 33 | 34 | #include "a_star.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/a_star.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "a_star.h" 5 | 6 | namespace graaf::algorithm { 7 | 8 | template 10 | requires std::is_invocable_r_v 11 | std::optional> a_star_search( 12 | const graph& graph, vertex_id_t start_vertex, 13 | vertex_id_t target_vertex, const HEURISTIC_T& heuristic) { 14 | // Define a priority queue for open set of vertices to explore. 15 | // This part is similar to dijkstra_shortest_path 16 | using weighted_path_item = detail::path_vertex; 17 | // The set of discovered vertices that may need to be (re-)expanded. 18 | // f_score represents the estimated total cost of the path from the start 19 | // vertex to the goal vertex through the current vertex. 20 | // It's a combination of g_score and h_score: 21 | // f_score[n] = g_score[n] + h_score[n] 22 | // For vertex n, prev_id in path_vertex is the vertex immediately preceding 23 | // it on the cheapest path from the start to n currently known. The priority 24 | // queue uses internally a binary heap. To get the minimum element, we use 25 | // the std::greater comparator. 26 | using a_star_queue_t = 27 | std::priority_queue, 28 | std::greater<>>; 29 | a_star_queue_t open_set{}; 30 | 31 | // For vertex n, g_score[n] is the cost of the cheapest path from start to n 32 | // currently known. It tracks the cost of reaching each vertex 33 | std::unordered_map g_score; 34 | // Initialize g_score map. 35 | g_score[start_vertex] = 0; 36 | 37 | std::unordered_map vertex_info; 38 | vertex_info[start_vertex] = { 39 | start_vertex, 40 | heuristic(start_vertex), // f_score[n] = g_score[n] + h(n), and 41 | // g_score[n] is 0 if n is start_vertex. 42 | start_vertex}; 43 | 44 | // Initialize start vertex in open set queue 45 | open_set.push(vertex_info[start_vertex]); 46 | 47 | while (!open_set.empty()) { 48 | // Get the vertex with the lowest f_score 49 | auto current{open_set.top()}; 50 | open_set.pop(); 51 | 52 | // Check if current vertex is the target 53 | if (current.id == target_vertex) { 54 | return reconstruct_path(start_vertex, target_vertex, vertex_info); 55 | } 56 | 57 | // Iterate through neighboring vertices 58 | for (const auto& neighbor : graph.get_neighbors(current.id)) { 59 | WEIGHT_T edge_weight = get_weight(graph.get_edge(current.id, neighbor)); 60 | 61 | // A* search does not work on negative edge weights. 62 | if (edge_weight < 0) { 63 | throw std::invalid_argument{fmt::format( 64 | "Negative edge weight [{}] between vertices [{}] -> [{}].", 65 | edge_weight, current.id, neighbor)}; 66 | } 67 | 68 | // tentative_g_score is the distance from start to the neighbor through 69 | // current_vertex 70 | WEIGHT_T tentative_g_score = g_score[current.id] + edge_weight; 71 | 72 | // Checks if vertex_info doesn't contain neighbor yet. 73 | // But if it contains it, and the tentative_g_score is smaller, 74 | // we need to update vertex_info and add it to the open set. 75 | if (!vertex_info.contains(neighbor) || 76 | tentative_g_score < g_score[neighbor]) { 77 | // This path to neighbor is better than any previous one, so we need 78 | // to update our data. Update neighbor's g_score, f_score and previous 79 | // vertex on the path 80 | g_score[neighbor] = tentative_g_score; 81 | auto f_score = tentative_g_score + heuristic(neighbor); 82 | 83 | // always update vertex_info[neighbor] 84 | vertex_info[neighbor] = { 85 | neighbor, // vertex id 86 | f_score, // f_score = tentantive_g_score + h(neighbor) 87 | current.id // neighbor vertex came from current vertex 88 | }; 89 | 90 | open_set.push(vertex_info[neighbor]); 91 | } 92 | } 93 | } 94 | 95 | // No path found 96 | return std::nullopt; 97 | } 98 | 99 | } // namespace graaf::algorithm 100 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/bellman_ford.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | /** 10 | * Find the shortest paths from a source vertex to all other vertices using 11 | * the Bellman-Ford algorithm. 12 | * 13 | * @tparam V The vertex type of the graph. 14 | * @tparam E The edge type of the graph. 15 | * @tparam T The graph specialization (directed or undirected). 16 | * @tparam WEIGHT_T The type of weight associated with the edges. 17 | * @param graph The graph in which to find the shortest paths. 18 | * @param start_vertex The source vertex for the shortest paths. 19 | * @return A map of target vertex IDs to shortest path structures. Each 20 | * value contains a graph_path object representing the 21 | * shortest path from the source vertex to the respective vertex. 22 | * If a vertex is unreachable from the source, its entry will be 23 | * absent from the map. 24 | */ 25 | template ()))> 27 | std::unordered_map> 28 | bellman_ford_shortest_paths(const graph& graph, 29 | vertex_id_t start_vertex); 30 | 31 | } // namespace graaf::algorithm 32 | 33 | #include "bellman_ford.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/bellman_ford.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "bellman_ford.h" 3 | 4 | namespace graaf::algorithm { 5 | 6 | template 7 | std::unordered_map> 8 | bellman_ford_shortest_paths(const graph& graph, 9 | vertex_id_t start_vertex) { 10 | std::unordered_map> shortest_paths; 11 | 12 | const auto found_shorter_path{ 13 | [&shortest_paths](edge_id_t edge_id, const E& edge) { 14 | const auto weight = get_weight(edge); 15 | const auto [u, v]{edge_id}; 16 | return shortest_paths[u].total_weight != 17 | std::numeric_limits::max() && 18 | shortest_paths[u].total_weight + weight < 19 | shortest_paths[v].total_weight; 20 | }}; 21 | 22 | // Initialize the shortest path distances from the starting vertex to 23 | // "infinity" for all vertices 24 | for (const auto& [vertex_id, _] : graph.get_vertices()) { 25 | shortest_paths[vertex_id] = {{vertex_id}, 26 | std::numeric_limits::max()}; 27 | } 28 | 29 | // Set the distance from the starting vertex to itself to 0 30 | shortest_paths[start_vertex] = {{start_vertex}, 0}; 31 | 32 | // Relax edges for |V| - 1 iterations 33 | for (std::size_t i = 1; i < graph.vertex_count(); ++i) { 34 | for (const auto& [edge_id, edge] : graph.get_edges()) { 35 | const auto [u, v]{edge_id}; 36 | WEIGHT_T weight = get_weight(edge); 37 | 38 | if (found_shorter_path(edge_id, edge)) { 39 | // Update the shortest path to vertex v 40 | shortest_paths[v] = { 41 | {shortest_paths[u].vertices}, 42 | shortest_paths[u].total_weight + weight, 43 | }; 44 | shortest_paths[v].vertices.push_back(v); 45 | } 46 | } 47 | } 48 | // Negative cycle detection by doing an additional pass in the graph 49 | for (const auto& [edge_id, edge] : graph.get_edges()) { 50 | const auto [u, v]{edge_id}; 51 | WEIGHT_T weight = get_weight(edge); 52 | if (found_shorter_path(edge_id, edge)) { 53 | throw std::invalid_argument{"Negative cycle detected in the graph."}; 54 | } 55 | } 56 | return shortest_paths; 57 | } 58 | 59 | } // namespace graaf::algorithm 60 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/bfs_shortest_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * @brief calculates the shortest path between one start_vertex and one 13 | * end_vertex using BFS. This does not consider edge weights. 14 | * 15 | * @param graph The graph to extract shortest path from. 16 | * @param start_vertex Vertex id where the shortest path should start. 17 | * @param end_vertex Vertex id where the shortest path should end. 18 | * @return An optional with the shortest path (list of vertices) if found. 19 | */ 20 | template ()))> 22 | std::optional> bfs_shortest_path( 23 | const graph& graph, vertex_id_t start_vertex, 24 | vertex_id_t end_vertex); 25 | 26 | } // namespace graaf::algorithm 27 | 28 | #include "bfs_shortest_path.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/bfs_shortest_path.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "bfs_shortest_path.h" 5 | 6 | namespace graaf::algorithm { 7 | 8 | template 9 | std::optional> bfs_shortest_path( 10 | const graph& graph, vertex_id_t start_vertex, 11 | vertex_id_t end_vertex) { 12 | std::unordered_map> vertex_info{ 13 | {start_vertex, {start_vertex, 0, start_vertex}}}; 14 | 15 | const auto callback{[&vertex_info](const edge_id_t& edge) { 16 | const auto [source, target]{edge}; 17 | 18 | if (!vertex_info.contains(target)) { 19 | vertex_info[target] = {target, vertex_info[source].dist_from_start + 1, 20 | source}; 21 | } 22 | }}; 23 | 24 | // We keep searching until we have reached the target vertex 25 | const auto search_termination_strategy{ 26 | [end_vertex](const vertex_id_t vertex_id) { 27 | return vertex_id == end_vertex; 28 | }}; 29 | 30 | breadth_first_traverse(graph, start_vertex, callback, 31 | search_termination_strategy); 32 | 33 | return reconstruct_path(start_vertex, end_vertex, vertex_info); 34 | } 35 | 36 | } // namespace graaf::algorithm 37 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | // Forward declaration 10 | template 11 | struct graph_path; 12 | 13 | namespace detail { 14 | 15 | template 16 | struct path_vertex { 17 | vertex_id_t id; 18 | WEIGHT_T dist_from_start; 19 | vertex_id_t prev_id; 20 | 21 | [[nodiscard]] bool operator>(const path_vertex& other) { 22 | return dist_from_start > other.dist_from_start; 23 | } 24 | }; 25 | 26 | template 27 | std::optional> reconstruct_path( 28 | vertex_id_t start_vertex, vertex_id_t end_vertex, 29 | std::unordered_map>& vertex_info); 30 | 31 | } // namespace detail 32 | 33 | template 34 | struct graph_path { 35 | std::list vertices; 36 | WEIGHT_T total_weight; 37 | 38 | bool operator==(const graph_path& other) const { 39 | return vertices == other.vertices && total_weight == other.total_weight; 40 | } 41 | }; 42 | 43 | } // namespace graaf::algorithm 44 | 45 | #include "common.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/common.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | 4 | namespace graaf::algorithm { 5 | 6 | namespace detail { 7 | 8 | template 9 | std::optional> reconstruct_path( 10 | vertex_id_t start_vertex, vertex_id_t end_vertex, 11 | std::unordered_map>& vertex_info) { 12 | if (!vertex_info.contains(end_vertex)) { 13 | return std::nullopt; 14 | } 15 | 16 | graph_path path; 17 | auto current = end_vertex; 18 | 19 | while (current != start_vertex) { 20 | path.vertices.push_front(current); 21 | current = vertex_info[current].prev_id; 22 | } 23 | 24 | path.vertices.push_front(start_vertex); 25 | path.total_weight = vertex_info[end_vertex].dist_from_start; 26 | return path; 27 | } 28 | 29 | } // namespace detail 30 | 31 | } // namespace graaf::algorithm 32 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/dijkstra_shortest_path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * @brief calculates the shortest path between one start_vertex and one 13 | * end_vertex using Dijkstra's algorithm. Works on both weighted as well as 14 | * unweighted graphs. For unweighted graphs, a unit weight is used for each 15 | * edge. 16 | * 17 | * @param graph The graph to extract shortest path from. 18 | * @param start_vertex Vertex id where the shortest path should start. 19 | * @param end_vertex Vertex id where the shortest path should end. 20 | * @return An optional with the shortest path (list of vertices) if found. 21 | */ 22 | template ()))> 24 | std::optional> dijkstra_shortest_path( 25 | const graph& graph, vertex_id_t start_vertex, 26 | vertex_id_t end_vertex); 27 | 28 | } // namespace graaf::algorithm 29 | 30 | #include "dijkstra_shortest_path.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/dijkstra_shortest_path.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "dijkstra_shortest_path.h" 6 | 7 | namespace graaf::algorithm { 8 | 9 | template 10 | std::optional> dijkstra_shortest_path( 11 | const graph& graph, vertex_id_t start_vertex, 12 | vertex_id_t end_vertex) { 13 | using weighted_path_item = detail::path_vertex; 14 | using dijkstra_queue_t = 15 | std::priority_queue, 16 | std::greater<>>; 17 | dijkstra_queue_t to_explore{}; 18 | std::unordered_map vertex_info; 19 | 20 | vertex_info[start_vertex] = {start_vertex, 0, start_vertex}; 21 | to_explore.push(vertex_info[start_vertex]); 22 | 23 | while (!to_explore.empty()) { 24 | auto current{to_explore.top()}; 25 | to_explore.pop(); 26 | 27 | if (current.id == end_vertex) { 28 | break; 29 | } 30 | 31 | for (const auto& neighbor : graph.get_neighbors(current.id)) { 32 | WEIGHT_T edge_weight = get_weight(graph.get_edge(current.id, neighbor)); 33 | 34 | if (edge_weight < 0) { 35 | std::ostringstream error_msg; 36 | error_msg << "Negative edge weight [" << edge_weight 37 | << "] between vertices [" << current.id << "] -> [" 38 | << neighbor << "]."; 39 | throw std::invalid_argument{error_msg.str()}; 40 | } 41 | 42 | WEIGHT_T distance = current.dist_from_start + edge_weight; 43 | 44 | if (!vertex_info.contains(neighbor) || 45 | distance < vertex_info[neighbor].dist_from_start) { 46 | vertex_info[neighbor] = {neighbor, distance, current.id}; 47 | to_explore.push(vertex_info[neighbor]); 48 | } 49 | } 50 | } 51 | 52 | return reconstruct_path(start_vertex, end_vertex, vertex_info); 53 | } 54 | 55 | } // namespace graaf::algorithm 56 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/dijkstra_shortest_paths.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | /** 10 | * Find the shortest paths from a source vertex to all other vertices in the 11 | * graph using Dijkstra's algorithm. 12 | * 13 | * @tparam V The vertex type of the graph. 14 | * @tparam E The edge type of the graph. 15 | * @tparam T The graph type (directed or undirected). 16 | * @tparam WEIGHT_T The type of edge weights. 17 | * @param graph The graph we want to search. 18 | * @param source_vertex The source vertex from which to compute shortest paths. 19 | * @return A map containing the shortest paths from the source vertex to all 20 | * other vertices. The map keys are target vertex IDs, and the values are 21 | * instances of graph_path, representing the shortest distance and the path 22 | * (list of vertex IDs) from the source to the target. If a vertex is not 23 | * reachable from the source, its entry will be absent from the map. 24 | */ 25 | template ()))> 27 | [[nodiscard]] std::unordered_map> 28 | dijkstra_shortest_paths(const graph& graph, vertex_id_t source_vertex); 29 | 30 | } // namespace graaf::algorithm 31 | 32 | #include "dijkstra_shortest_paths.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/dijkstra_shortest_paths.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "dijkstra_shortest_paths.h" 6 | 7 | namespace graaf::algorithm { 8 | 9 | template 10 | [[nodiscard]] std::unordered_map> 11 | dijkstra_shortest_paths(const graph& graph, 12 | vertex_id_t source_vertex) { 13 | std::unordered_map> shortest_paths; 14 | 15 | using weighted_path_item = detail::path_vertex; 16 | using dijkstra_queue_t = 17 | std::priority_queue, 18 | std::greater<>>; 19 | dijkstra_queue_t to_explore{}; 20 | 21 | shortest_paths[source_vertex].total_weight = 0; 22 | shortest_paths[source_vertex].vertices.push_back(source_vertex); 23 | to_explore.push(weighted_path_item{source_vertex, 0}); 24 | 25 | while (!to_explore.empty()) { 26 | auto current{to_explore.top()}; 27 | to_explore.pop(); 28 | 29 | if (shortest_paths.contains(current.id) && 30 | current.dist_from_start > shortest_paths[current.id].total_weight) { 31 | continue; 32 | } 33 | 34 | for (const auto neighbor : graph.get_neighbors(current.id)) { 35 | WEIGHT_T edge_weight = get_weight(graph.get_edge(current.id, neighbor)); 36 | 37 | if (edge_weight < 0) { 38 | std::ostringstream error_msg; 39 | error_msg << "Negative edge weight [" << edge_weight 40 | << "] between vertices [" << current.id << "] -> [" 41 | << neighbor << "]."; 42 | throw std::invalid_argument{error_msg.str()}; 43 | } 44 | 45 | WEIGHT_T distance = current.dist_from_start + edge_weight; 46 | 47 | if (!shortest_paths.contains(neighbor) || 48 | distance < shortest_paths[neighbor].total_weight) { 49 | shortest_paths[neighbor].total_weight = distance; 50 | shortest_paths[neighbor].vertices = shortest_paths[current.id].vertices; 51 | shortest_paths[neighbor].vertices.push_back(neighbor); 52 | to_explore.push(weighted_path_item{neighbor, distance}); 53 | } 54 | } 55 | } 56 | 57 | return shortest_paths; 58 | } 59 | 60 | } // namespace graaf::algorithm 61 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/floyd_warshall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace graaf::algorithm { 7 | /** 8 | * @brief Floyd-Warshall Algorithm 9 | * 10 | * This function computes the shortest paths between all pairs of vertices in a 11 | * given weighted graph. It works for graphs with negative weight edges as well, 12 | * but not for graphs with negative weight cycles. The function returns an 13 | * adjacency matrix representing the shortest distances. 14 | * 15 | * @tparam V The type of a graph vertex 16 | * @tparam E The type of a graph edge 17 | * @tparam T the graph type (DIRECTED or UNDIRECTED) 18 | * @tparam WEIGHT_T The weight type of an edge in the graph 19 | * @param graph The graph object 20 | * @return A 2D vector where element at [i][j] is the shortest distance from 21 | * vertex i to vertex j. 22 | */ 23 | template ()))> 25 | std::vector> floyd_warshall_shortest_paths( 26 | const graph& graph); 27 | 28 | }; // namespace graaf::algorithm 29 | 30 | #include "floyd_warshall.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/shortest_path/floyd_warshall.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "floyd_warshall.h" 8 | 9 | namespace graaf::algorithm { 10 | 11 | template 12 | std::vector> floyd_warshall_shortest_paths( 13 | const graph& graph) { 14 | WEIGHT_T ZERO{}; 15 | std::size_t n = graph.vertex_count(); 16 | auto INF = std::numeric_limits::max(); 17 | 18 | std::vector> shortest_paths( 19 | n, std::vector(n, INF)); 20 | 21 | for (std::size_t vertex = 0; vertex < n; ++vertex) { 22 | shortest_paths[vertex][vertex] = ZERO; 23 | } 24 | 25 | // Initial weights between vertices 26 | for (std::size_t from_vertex = 0; from_vertex < n; ++from_vertex) { 27 | for (const auto& to_vertex : graph.get_neighbors(from_vertex)) { 28 | shortest_paths[from_vertex][to_vertex] = 29 | std::min(shortest_paths[from_vertex][to_vertex], 30 | get_weight(graph.get_edge(from_vertex, to_vertex))); 31 | } 32 | } 33 | 34 | for (std::size_t through_vertex = 0; through_vertex < n; ++through_vertex) { 35 | for (std::size_t start_vertex = 0; start_vertex < n; ++start_vertex) { 36 | if (shortest_paths[start_vertex][through_vertex] < INF) { 37 | for (std::size_t end_vertex = 0; end_vertex < n; ++end_vertex) { 38 | if (shortest_paths[through_vertex][end_vertex] < INF) { 39 | shortest_paths[start_vertex][end_vertex] = 40 | std::min(shortest_paths[start_vertex][end_vertex], 41 | shortest_paths[start_vertex][through_vertex] + 42 | shortest_paths[through_vertex][end_vertex]); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | return shortest_paths; 50 | } 51 | 52 | }; // namespace graaf::algorithm 53 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/strongly_connected_components/common.h: -------------------------------------------------------------------------------- 1 | #pragma 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | using sccs_t = std::vector>; 10 | 11 | } // namespace graaf::algorithm -------------------------------------------------------------------------------- /include/graaflib/algorithm/strongly_connected_components/kosaraju.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace graaf::algorithm { 10 | 11 | /** 12 | * @brief Finds strongly connected components in a directed graph using 13 | * Kosaraju's algorithm. 14 | * 15 | * Kosaraju's algorithm identifies strongly connected components (SCCs) in a 16 | * directed graph. The algorithm uses two depth-first searches to discover these 17 | * components. 18 | * 19 | * @tparam V The vertex type of the graph. 20 | * @tparam E The edge type of the graph. 21 | * @param graph The input directed graph. 22 | * 23 | * @return An sccs_t, a type representing an std::vector of vectors, 24 | * each of which contains the vertex IDs forming a strongly connected 25 | * component. 26 | */ 27 | template 28 | sccs_t kosarajus_strongly_connected_components( 29 | const directed_graph& graph); 30 | 31 | } // namespace graaf::algorithm 32 | 33 | #include "kosaraju.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/strongly_connected_components/kosaraju.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "kosaraju.h" 10 | 11 | namespace graaf::algorithm { 12 | 13 | namespace { 14 | 15 | template 16 | void do_visit_vertex(const vertex_id_t vertex_id, 17 | const directed_graph& graph, 18 | std::stack& stack, 19 | std::unordered_set& seen_vertices) { 20 | if (!seen_vertices.contains(vertex_id)) { 21 | seen_vertices.insert(vertex_id); 22 | for (const auto neighbour : graph.get_neighbors(vertex_id)) { 23 | do_visit_vertex(neighbour, graph, stack, seen_vertices); 24 | } 25 | stack.push(vertex_id); 26 | } 27 | } 28 | 29 | template 30 | void make_strongly_connected_component_from_vertex( 31 | const vertex_id_t vertex, const directed_graph& transposed_graph, 32 | std::vector& scc, 33 | std::unordered_set& seen_vertices) { 34 | if (!seen_vertices.contains(vertex)) { 35 | seen_vertices.insert(vertex); 36 | scc.push_back(vertex); 37 | 38 | for (const auto neighbour : transposed_graph.get_neighbors(vertex)) { 39 | make_strongly_connected_component_from_vertex(neighbour, transposed_graph, 40 | scc, seen_vertices); 41 | } 42 | } 43 | } 44 | 45 | } // namespace 46 | 47 | template 48 | sccs_t kosarajus_strongly_connected_components( 49 | const directed_graph& graph) { 50 | sccs_t sccs{}; 51 | 52 | if (graph.get_vertices().size() == 0) { 53 | return sccs; 54 | } 55 | 56 | std::stack stack{}; 57 | std::unordered_set seen_vertices{}; 58 | 59 | for (const auto& [vertex_id, vertex] : graph.get_vertices()) { 60 | if (!seen_vertices.contains(vertex_id)) { 61 | do_visit_vertex(vertex_id, graph, stack, seen_vertices); 62 | } 63 | } 64 | 65 | seen_vertices.clear(); 66 | 67 | const auto transposed_graph = get_transposed_graph(graph); 68 | std::vector scc{}; 69 | 70 | while (!stack.empty()) { 71 | if (!seen_vertices.contains(stack.top())) { 72 | scc.clear(); 73 | make_strongly_connected_component_from_vertex( 74 | stack.top(), transposed_graph, scc, seen_vertices); 75 | sccs.push_back(scc); 76 | } 77 | 78 | stack.pop(); 79 | } 80 | 81 | return sccs; 82 | } 83 | 84 | } // namespace graaf::algorithm 85 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/strongly_connected_components/tarjan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | /** 10 | * Computes the Strongly Connected Components (SCCs) of a graph using Tarjan's 11 | * algorithm. 12 | * 13 | * This function takes a graph and returns a vector of vectors representing the 14 | * SCCs. Each inner vector contains the vertices of a strongly connected 15 | * component, and the outer vector contains all the strongly connected 16 | * components in the graph. 17 | * 18 | * @tparam V Vertex type. 19 | * @tparam E Edge type. 20 | * @param graph The graph for which to compute SCCs. 21 | * @return An sccs_t, a type representing an std::vector of vectors, 22 | * each of which contains the vertex IDs forming a strongly connected 23 | * component. 24 | */ 25 | template 26 | [[nodiscard]] sccs_t tarjans_strongly_connected_components( 27 | const graph& graph); 28 | 29 | } // namespace graaf::algorithm 30 | 31 | #include "tarjan.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/strongly_connected_components/tarjan.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include // For std::function 5 | #include 6 | #include 7 | #include 8 | 9 | #include "tarjan.h" 10 | 11 | namespace graaf::algorithm { 12 | 13 | template 14 | [[nodiscard]] sccs_t tarjans_strongly_connected_components( 15 | const graph& graph) { 16 | // Vector to store strongly connected components 17 | sccs_t sccs{}; 18 | 19 | // Stack to hold vertices during traversal 20 | std::stack stack; 21 | 22 | // Maps to keep track of indices, low-link values, and stack membership 23 | std::unordered_map indices{}; 24 | std::unordered_map low_links{}; 25 | std::unordered_map on_stack{}; 26 | 27 | // Counter for indexing vertices 28 | size_t index_counter = 0; 29 | 30 | // Lambda function for the strong connect traversal 31 | std::function strong_connect; 32 | 33 | strong_connect = [&](vertex_id_t vertex) { 34 | // Set indices and low-link values for the current vertex 35 | indices[vertex] = index_counter; 36 | low_links[vertex] = index_counter; 37 | index_counter++; 38 | 39 | // Push the vertex onto the stack and mark it as on-stack 40 | stack.push(vertex); 41 | on_stack[vertex] = true; 42 | 43 | // Traverse neighbors 44 | for (const auto neighbor : graph.get_neighbors(vertex)) { 45 | if (!indices.contains(neighbor)) { 46 | // Neighbor has not yet been visited; recurse on it 47 | strong_connect(neighbor); 48 | low_links[vertex] = std::min(low_links[vertex], low_links[neighbor]); 49 | } else if (on_stack[neighbor]) { 50 | // Neighbor is in stack and hence in the current SCC 51 | low_links[vertex] = std::min(low_links[vertex], indices[neighbor]); 52 | } 53 | } 54 | 55 | // If low-link and index match, a strongly connected component is found 56 | if (low_links[vertex] == indices[vertex]) { 57 | std::vector scc; 58 | vertex_id_t top; 59 | // Pop vertices from the stack to form the SCC 60 | do { 61 | top = stack.top(); 62 | stack.pop(); 63 | on_stack[top] = false; 64 | scc.push_back(top); // Add to current strongly connected component 65 | } while (top != vertex); 66 | 67 | // Add the SCC to the list of SCCs 68 | sccs.push_back(scc); 69 | } 70 | }; 71 | 72 | // Traverse all vertices to find SCCs 73 | for (const auto& [vertex_id, vertex] : graph.get_vertices()) { 74 | if (!indices.contains(vertex_id)) { 75 | strong_connect(vertex_id); 76 | } 77 | } 78 | 79 | return sccs; 80 | } 81 | 82 | } // namespace graaf::algorithm 83 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/topological_sorting/dfs_topological_sorting.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | /** 9 | * @brief Calculates order of vertices in topological order 10 | * using DFS traversal 11 | * 12 | * @tparam V The vertex type of the graph. 13 | * @tparam E The edge type of the graph. 14 | * @param graph The input graph. 15 | * @return Vector of vertices sorted in topological order 16 | */ 17 | template 18 | [[nodiscard]] std::optional> dfs_topological_sort( 19 | const graph& graph); 20 | 21 | } // namespace graaf::algorithm 22 | #include "dfs_topological_sorting.tpp" 23 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/topological_sorting/dfs_topological_sorting.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dfs_topological_sorting.h" 10 | 11 | namespace graaf::algorithm { 12 | 13 | namespace detail { 14 | 15 | // DFS topological sort 16 | template 17 | void do_dfs_topological_sort( 18 | const graph& graph, vertex_id_t start_vertex, 19 | std::unordered_set& processed_vertices, 20 | std::vector& sorted_vertices) { 21 | processed_vertices.insert(start_vertex); 22 | for (const auto& next_vertex : graph.get_neighbors(start_vertex)) { 23 | if (!processed_vertices.contains(next_vertex)) { 24 | do_dfs_topological_sort(graph, next_vertex, processed_vertices, 25 | sorted_vertices); 26 | } 27 | } 28 | 29 | sorted_vertices.push_back(start_vertex); 30 | } 31 | 32 | }; // namespace detail 33 | 34 | template 35 | std::optional> dfs_topological_sort( 36 | const graph& graph) { 37 | // Graph should be acyclic 38 | if (dfs_cycle_detection(graph)) { 39 | return std::nullopt; 40 | } 41 | 42 | std::vector sorted_vertices{}; 43 | sorted_vertices.reserve(graph.vertex_count()); 44 | std::unordered_set processed_vertices{}; 45 | 46 | for (const auto& [vertex_id, _] : graph.get_vertices()) { 47 | if (!processed_vertices.contains(vertex_id)) { 48 | detail::do_dfs_topological_sort(graph, vertex_id, processed_vertices, 49 | sorted_vertices); 50 | } 51 | } 52 | 53 | std::reverse(sorted_vertices.begin(), sorted_vertices.end()); 54 | return sorted_vertices; 55 | } 56 | 57 | }; // namespace graaf::algorithm 58 | -------------------------------------------------------------------------------- /include/graaflib/algorithm/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace graaf { 6 | 7 | /** 8 | * Get transposed version of a given directed graph 9 | * 10 | * @param graph The directed graph that is to be transposed 11 | * @return directed_graph The transposed graph 12 | */ 13 | template 14 | directed_graph get_transposed_graph( 15 | const directed_graph& graph); 16 | 17 | } // namespace graaf 18 | 19 | #include "utils.tpp" -------------------------------------------------------------------------------- /include/graaflib/algorithm/utils.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | 4 | namespace graaf { 5 | 6 | template 7 | directed_graph get_transposed_graph( 8 | const directed_graph& graph) { 9 | directed_graph transposed_graph{}; 10 | for (auto [edge_vertices, edge_type] : graph.get_edges()) { 11 | const auto [vertex_id_lhs, vertex_id_rhs] = edge_vertices; 12 | 13 | auto vertex_value_lhs = graph.get_vertex(vertex_id_lhs); 14 | auto vertex_value_rhs = graph.get_vertex(vertex_id_rhs); 15 | 16 | if (!transposed_graph.has_vertex(vertex_id_lhs)) { 17 | auto vertex_id = transposed_graph.add_vertex(std::move(vertex_value_lhs), 18 | vertex_id_lhs); 19 | } 20 | 21 | if (!transposed_graph.has_vertex(vertex_id_rhs)) { 22 | auto vertex_id = transposed_graph.add_vertex(std::move(vertex_value_rhs), 23 | vertex_id_rhs); 24 | } 25 | 26 | transposed_graph.add_edge(vertex_id_rhs, vertex_id_lhs, 27 | std::move(edge_type)); 28 | } 29 | 30 | return transposed_graph; 31 | } 32 | 33 | } // namespace graaf 34 | -------------------------------------------------------------------------------- /include/graaflib/edge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace graaf { 7 | 8 | /** 9 | * @brief Interface for a weighted edge. 10 | * 11 | * This is what is stored internally and returned from a weighted graph in 12 | * order to make sure each edge in a weighted graph has a common interface to 13 | * extract the weight. 14 | * 15 | * @tparam WEIGHT_T The type of the weight. 16 | */ 17 | template 18 | class weighted_edge { 19 | public: 20 | using weight_t = WEIGHT_T; 21 | 22 | virtual ~weighted_edge() = default; 23 | 24 | [[nodiscard]] virtual WEIGHT_T get_weight() const noexcept = 0; 25 | }; 26 | 27 | template 28 | concept derived_from_weighted_edge = 29 | std::is_base_of_v, derived>; 30 | 31 | /** 32 | * Overload set to get the weight from an edge 33 | */ 34 | template 35 | requires derived_from_weighted_edge 36 | [[nodiscard]] auto get_weight(const WEIGHTED_EDGE_T& edge); 37 | 38 | template 39 | requires std::is_arithmetic_v 40 | [[nodiscard]] EDGE_T get_weight(const EDGE_T& edge); 41 | 42 | template 43 | [[nodiscard]] int get_weight(const EDGE_T& /*edge*/); 44 | 45 | } // namespace graaf 46 | 47 | #include "edge.tpp" -------------------------------------------------------------------------------- /include/graaflib/edge.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "edge.h" 3 | 4 | namespace graaf { 5 | 6 | template 7 | requires derived_from_weighted_edge 8 | auto get_weight(const WEIGHTED_EDGE_T& edge) { 9 | return edge.get_weight(); 10 | } 11 | 12 | template 13 | requires std::is_arithmetic_v 14 | EDGE_T get_weight(const EDGE_T& edge) { 15 | return edge; 16 | } 17 | 18 | template 19 | int get_weight(const EDGE_T& /*edge*/) { 20 | // By default, an edge has unit weight 21 | return 1; 22 | } 23 | 24 | } // namespace graaf 25 | -------------------------------------------------------------------------------- /include/graaflib/io/dot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace graaf::io { 11 | 12 | namespace detail { 13 | 14 | template 15 | concept string_convertible = requires(T element) { std::to_string(element); }; 16 | 17 | template 18 | requires string_convertible 19 | const auto default_vertex_writer{ 20 | [](vertex_id_t vertex_id, const T& vertex) -> std::string { 21 | // TODO(bluppes): replace with std::format once Clang supports it 22 | return "label=\"" + std::to_string(vertex_id) + ": " + 23 | std::to_string(vertex) + "\""; 24 | }}; 25 | 26 | const auto default_edge_writer{ 27 | [](const edge_id_t& /*edge_id*/, const auto& edge) -> std::string { 28 | // TODO(bluppes): replace with std::format once Clang supports it 29 | return "label=\"" + std::to_string(get_weight(edge)) + "\""; 30 | }}; 31 | } // namespace detail 32 | 33 | /** 34 | * @brief Serializes a graph to dot format and writes the result to a file. 35 | * 36 | * @tparam V The vertex type of the graph. 37 | * @tparam E The edge type of the graph. 38 | * @param graph The graph we want to serialize. 39 | * @param vertex_writer Function used for serializing the vertices. Should 40 | * accept a vertex_id_t and a type V and serialize it to a string. Default 41 | * implementations are provided for primitive numeric types. 42 | * @param edge_writer Function used for serializing the edges. Should accept an 43 | * edge_id_t and a graph::edge_t and serialize it to a string. Default 44 | * implementations are provided for primitive numeric types. 45 | * @param path Path to the output dot file. 46 | */ 47 | template ), 49 | typename EDGE_WRITER_T = decltype(detail::default_edge_writer)> 50 | requires std::is_invocable_r_v && 52 | std::is_invocable_r_v::edge_t&> 55 | void to_dot( 56 | const graph& graph, const std::filesystem::path& path, 57 | const VERTEX_WRITER_T& vertex_writer = detail::default_vertex_writer, 58 | const EDGE_WRITER_T& edge_writer = detail::default_edge_writer); 59 | 60 | } // namespace graaf::io 61 | 62 | #include "dot.tpp" -------------------------------------------------------------------------------- /include/graaflib/io/dot.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "dot.h" 8 | 9 | namespace graaf::io { 10 | 11 | namespace detail { 12 | 13 | /** 14 | * @brief Converts a graph specialization to the correct dot keyword. 15 | * 16 | * DIRECTED: graph 17 | * UNDIRECTED: digraph 18 | * 19 | * @param spec The graph specialization 20 | * @return constexpr const char* String with the correct dot keyword 21 | */ 22 | constexpr const char* graph_type_to_string(const graph_type& type) { 23 | switch (type) { 24 | using enum graph_type; 25 | case DIRECTED: 26 | return "digraph"; 27 | break; 28 | case UNDIRECTED: 29 | return "graph"; 30 | break; 31 | // LCOV_EXCL_START 32 | default: 33 | // We should never reach this 34 | std::abort(); 35 | // LCOV_EXCL_STOP 36 | } 37 | } 38 | 39 | /** 40 | * @brief Converts a graph specialization to the correct dot edge specifier. 41 | * 42 | * DIRECTED: -> 43 | * UNDIRECTED: -- 44 | * 45 | * @param spec The graph specialization 46 | * @return constexpr const char* String with the correct dot edge specifier 47 | */ 48 | constexpr const char* graph_type_to_edge_specifier(const graph_type& type) { 49 | switch (type) { 50 | using enum graph_type; 51 | case DIRECTED: 52 | return "->"; 53 | break; 54 | case UNDIRECTED: 55 | return "--"; 56 | break; 57 | // LCOV_EXCL_START 58 | default: 59 | // We should never reach this 60 | std::abort(); 61 | // LCOV_EXCL_STOP 62 | } 63 | } 64 | } // namespace detail 65 | 66 | template 68 | requires std::is_invocable_r_v && 70 | std::is_invocable_r_v::edge_t&> 73 | void to_dot(const graph& graph, const std::filesystem::path& path, 74 | const VERTEX_WRITER_T& vertex_writer, 75 | const EDGE_WRITER_T& edge_writer) { 76 | std::ofstream dot_file{path}; 77 | 78 | const auto append_line{ 79 | [&dot_file](const std::string& line) { dot_file << line << std::endl; }}; 80 | 81 | // TODO(bluppes): replace with std::format once Clang supports it 82 | append_line(std::string(detail::graph_type_to_string(T)) + " {"); 83 | 84 | for (const auto& [vertex_id, vertex] : graph.get_vertices()) { 85 | append_line("\t" + std::to_string(vertex_id) + " [" + 86 | vertex_writer(vertex_id, vertex) + "];"); 87 | } 88 | 89 | const auto edge_specifier{detail::graph_type_to_edge_specifier(T)}; 90 | for (const auto& [edge_id, edge] : graph.get_edges()) { 91 | const auto [source_id, target_id]{edge_id}; 92 | append_line("\t" + std::to_string(source_id) + " " + edge_specifier + " " + 93 | std::to_string(target_id) + " [" + edge_writer(edge_id, edge) + 94 | "];"); 95 | } 96 | 97 | append_line("}"); 98 | } 99 | 100 | } // namespace graaf::io 101 | -------------------------------------------------------------------------------- /include/graaflib/properties/vertex_properties.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace graaf::properties { 7 | 8 | template 9 | [[nodiscard]] std::size_t vertex_degree(const graaf::graph& graph, 10 | vertex_id_t vertex_id); 11 | 12 | template 13 | [[nodiscard]] std::size_t vertex_outdegree(const graaf::graph& graph, 14 | vertex_id_t vertex_id); 15 | 16 | template 17 | [[nodiscard]] std::size_t vertex_indegree(const graaf::graph& graph, 18 | vertex_id_t vertex_id); 19 | 20 | } // namespace graaf::properties 21 | 22 | #include "vertex_properties.tpp" 23 | -------------------------------------------------------------------------------- /include/graaflib/properties/vertex_properties.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "vertex_properties.h" 5 | 6 | namespace graaf::properties { 7 | 8 | template 9 | std::size_t vertex_degree(const graaf::graph& graph, 10 | vertex_id_t vertex_id) { 11 | if constexpr (T == graph_type::DIRECTED) { 12 | return vertex_outdegree(graph, vertex_id) + 13 | vertex_indegree(graph, vertex_id); 14 | } 15 | 16 | if constexpr (T == graph_type::UNDIRECTED) { 17 | return vertex_outdegree(graph, vertex_id); 18 | } 19 | 20 | // Should never reach this 21 | std::abort(); 22 | } 23 | 24 | template 25 | std::size_t vertex_outdegree(const graaf::graph& graph, 26 | vertex_id_t vertex_id) { 27 | return (graph.get_neighbors(vertex_id)).size(); 28 | } 29 | 30 | template 31 | std::size_t vertex_indegree(const graaf::graph& graph, 32 | vertex_id_t vertex_id) { 33 | using vertex_id_to_vertex_t = std::unordered_map; 34 | 35 | if constexpr (T == graph_type::DIRECTED) { 36 | return std::ranges::count_if( 37 | graph.get_vertices(), 38 | [&graph, 39 | vertex_id](const typename vertex_id_to_vertex_t::value_type& kv_pair) { 40 | const auto& [current_vertex_id, _]{kv_pair}; 41 | return graph.get_neighbors(current_vertex_id).contains(vertex_id); 42 | }); 43 | } 44 | 45 | if constexpr (T == graph_type::UNDIRECTED) { 46 | // For an undirected graph, the indegree of a vertex is equal to the 47 | // outdegree 48 | return vertex_outdegree(graph, vertex_id); 49 | } 50 | 51 | // Should never reach this 52 | std::abort(); 53 | } 54 | 55 | } // namespace graaf::properties 56 | -------------------------------------------------------------------------------- /include/graaflib/tree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace graaf { 10 | 11 | template 12 | class tree { 13 | public: 14 | // fwd 15 | struct tree_node; 16 | struct child_link; 17 | 18 | explicit tree(VERTEX_T root_val) 19 | : root_{std::make_unique(std::move(root_val), nullptr, 20 | std::vector{})} {} 21 | 22 | [[nodiscard]] tree_node* root() { return root_.get(); } 23 | [[nodiscard]] const tree_node* root() const { return root_.get(); } 24 | 25 | struct tree_node { 26 | // TODO(b.luppes): we are leaking implementation details regarding memory 27 | // management of children using std::unique_ptr. Consider providing a 28 | // non-owning view into the children. 29 | VERTEX_T value{}; 30 | tree_node* parent{}; // raw pointer to break cyclic dependency 31 | std::vector children{}; 32 | 33 | [[nodiscard]] tree_node* add_child(EDGE_T edge_val, VERTEX_T child_val); 34 | }; 35 | 36 | struct child_link { 37 | EDGE_T value{}; 38 | std::unique_ptr child{}; 39 | }; 40 | 41 | private: 42 | std::unique_ptr root_{}; 43 | }; 44 | 45 | } // namespace graaf 46 | 47 | #include "tree.tpp" 48 | -------------------------------------------------------------------------------- /include/graaflib/tree.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tree.h" 4 | 5 | namespace graaf { 6 | 7 | template 8 | tree::tree_node* tree::tree_node::add_child( 9 | EDGE_T edge_val, VERTEX_T child_val) { 10 | auto child{std::make_unique(std::move(child_val), this, 11 | std::vector{})}; 12 | children.emplace_back(std::move(edge_val), std::move(child)); 13 | return children.back().child.get(); 14 | } 15 | 16 | } // namespace graaf 17 | -------------------------------------------------------------------------------- /include/graaflib/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace graaf { 8 | 9 | using vertex_id_t = std::size_t; 10 | using edge_id_t = std::pair; 11 | 12 | /** 13 | * Based on boost::hash_combine. Since Boost is licensed under the Boost 14 | * Software License, we include a copy of the license here. 15 | * TODO(b.luppes): consider using Boost's new hash_combine implementation 16 | * 17 | * Boost Software License - Version 1.0 - August 17th, 2003 18 | * 19 | * Permission is hereby granted, free of charge, to any person or organization 20 | * obtaining a copy of the software and accompanying documentation covered by 21 | * this license (the "Software") to use, reproduce, display, distribute, 22 | * execute, and transmit the Software, and to prepare derivative works of the 23 | * Software, and to permit third-parties to whom the Software is furnished to 24 | * do so, all subject to the following: 25 | * 26 | * The copyright notices in the Software and this entire statement, including 27 | * the above license grant, this restriction and the following disclaimer, 28 | * must be included in all copies of the Software, in whole or in part, and 29 | * all derivative works of the Software, unless such copies or derivative 30 | * works are solely in the form of machine-executable object code generated by 31 | * a source language processor. 32 | * 33 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 36 | * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 37 | * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 38 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 39 | * DEALINGS IN THE SOFTWARE. 40 | * 41 | */ 42 | template 43 | inline void hash_combine(std::size_t& seed, const T& v) { 44 | std::hash hasher; 45 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 46 | } 47 | 48 | struct edge_id_hash { 49 | [[nodiscard]] std::size_t operator()(const edge_id_t& key) const { 50 | size_t seed = 0; 51 | hash_combine(seed, key.first); 52 | hash_combine(seed, key.second); 53 | 54 | return seed; 55 | } 56 | }; 57 | 58 | } // namespace graaf 59 | -------------------------------------------------------------------------------- /perf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The benchmarks are based on the google benchmark framework 2 | include(FetchContent) 3 | FetchContent_Declare( 4 | google_benchmark 5 | GIT_REPOSITORY https://github.com/google/benchmark.git 6 | GIT_TAG v1.8.2 7 | ) 8 | set(BENCHMARK_ENABLE_TESTING OFF) # Disable building the tests 9 | FetchContent_MakeAvailable(google_benchmark) 10 | 11 | # Include fmt as a dependency 12 | FetchContent_Declare( 13 | fmt 14 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 15 | GIT_TAG 9.1.0 16 | ) 17 | FetchContent_MakeAvailable(fmt) 18 | 19 | file(GLOB PERF_SOURCES "graaflib/*.cpp" "graaflib/*/*.cpp") 20 | add_executable( 21 | ${PROJECT_NAME}_perf 22 | ${PERF_SOURCES} 23 | ) 24 | 25 | # Include src such that we can #include in the sources 26 | target_include_directories(${PROJECT_NAME}_perf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include) 27 | 28 | target_link_libraries( 29 | ${PROJECT_NAME}_perf 30 | PRIVATE 31 | benchmark 32 | fmt::fmt 33 | ) -------------------------------------------------------------------------------- /perf/graaflib/add_edge_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace { 7 | 8 | template 9 | [[nodiscard]] std::vector create_vertices( 10 | graaf::directed_graph& graph, size_t n) { 11 | std::vector vertices{}; 12 | vertices.reserve(n); 13 | 14 | for (size_t i{0}; i < n; ++i) { 15 | vertices.push_back(graph.add_vertex(i)); 16 | } 17 | 18 | return vertices; 19 | } 20 | 21 | static void bm_add_primitive_numeric_edge(benchmark::State& state) { 22 | const auto number_of_edges{static_cast(state.range(0))}; 23 | 24 | graaf::directed_graph graph{}; 25 | 26 | // We create enough vertices to construct the requested number of edges 27 | const auto number_of_vertices{number_of_edges + 1}; 28 | const auto vertices{create_vertices(graph, number_of_vertices)}; 29 | 30 | for (auto _ : state) { 31 | for (size_t i{0}; i < number_of_edges; ++i) { 32 | graph.add_edge(vertices[i], vertices[i + 1], i); 33 | } 34 | } 35 | } 36 | 37 | struct edge { 38 | // Something which benefits from move construction 39 | std::vector data{100}; 40 | }; 41 | 42 | static void bm_add_user_defined_edge(benchmark::State& state) { 43 | const auto number_of_edges{static_cast(state.range(0))}; 44 | 45 | graaf::directed_graph graph{}; 46 | 47 | // We create enough vertices to construct the requested number of edges 48 | const auto number_of_vertices{number_of_edges + 1}; 49 | const auto vertices{create_vertices(graph, number_of_vertices)}; 50 | 51 | for (auto _ : state) { 52 | for (size_t i{0}; i < number_of_edges; ++i) { 53 | graph.add_edge(vertices[i], vertices[i + 1], edge{}); 54 | } 55 | } 56 | } 57 | 58 | } // namespace 59 | 60 | // Register the benchmarks 61 | BENCHMARK(bm_add_primitive_numeric_edge)->Range(1'000, 10'000); 62 | BENCHMARK(bm_add_user_defined_edge)->Range(1'000, 10'000); -------------------------------------------------------------------------------- /perf/graaflib/add_vertex_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace { 7 | 8 | static void bm_add_primitive_numeric_vertex(benchmark::State& state) { 9 | const auto number_of_vertices{static_cast(state.range(0))}; 10 | 11 | graaf::directed_graph graph{}; 12 | for (auto _ : state) { 13 | for (size_t i{0}; i < number_of_vertices; ++i) { 14 | [[maybe_unused]] const auto vertex_id{graph.add_vertex(i)}; 15 | } 16 | } 17 | } 18 | 19 | struct vertex { 20 | // Something which benefits from move construction 21 | std::vector data{100}; 22 | }; 23 | 24 | static void bm_add_user_defined_vertex(benchmark::State& state) { 25 | const auto number_of_vertices{static_cast(state.range(0))}; 26 | 27 | graaf::directed_graph graph{}; 28 | for (auto _ : state) { 29 | for (size_t i{0}; i < number_of_vertices; ++i) { 30 | [[maybe_unused]] const auto vertex_id{graph.add_vertex(vertex{})}; 31 | } 32 | } 33 | } 34 | 35 | } // namespace 36 | 37 | // Register the benchmarks 38 | BENCHMARK(bm_add_primitive_numeric_vertex)->Range(1'000, 10'000'000); 39 | BENCHMARK(bm_add_user_defined_vertex)->Range(1'000, 10'000'000); -------------------------------------------------------------------------------- /perf/graaflib/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Run all registered benchmarks 4 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The tests are based on the GTEST framework 2 | include(FetchContent) 3 | FetchContent_Declare( 4 | googletest 5 | GIT_REPOSITORY https://github.com/google/googletest.git 6 | GIT_TAG release-1.12.1 7 | ) 8 | # For Windows: Prevent overriding the parent project's compiler/linker settings 9 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 10 | FetchContent_MakeAvailable(googletest) 11 | 12 | # Include fmt/fmtlib as a dependency 13 | include(FetchContent) 14 | FetchContent_Declare( 15 | fmt 16 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 17 | GIT_TAG 9.1.0 18 | ) 19 | FetchContent_MakeAvailable(fmt) 20 | 21 | file(GLOB_RECURSE TEST_SOURCES "./*.cpp") 22 | add_executable( 23 | ${PROJECT_NAME}_test 24 | ${TEST_SOURCES} 25 | ) 26 | 27 | if (ENABLE_COVERAGE) 28 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../cmake-modules) 29 | include(CodeCoverage) 30 | append_coverage_compiler_flags_to_target(${PROJECT_NAME}_test) 31 | 32 | setup_target_for_coverage_lcov( 33 | NAME ctest_coverage 34 | EXECUTABLE ctest -j ${PROCESSOR_COUNT} 35 | DEPENDENCIES ${PROJECT_NAME}_test 36 | NO_DEMANGLE 37 | ) 38 | endif () 39 | 40 | # Include src such that we can #include and #include in the sources 41 | target_include_directories(${PROJECT_NAME}_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include) 42 | target_include_directories(${PROJECT_NAME}_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 43 | 44 | target_link_libraries( 45 | ${PROJECT_NAME}_test 46 | PRIVATE 47 | gtest_main 48 | fmt::fmt 49 | ) 50 | 51 | # Enable CMAKE's test runner to discover tests 52 | include(GoogleTest) 53 | gtest_discover_tests(${PROJECT_NAME}_test) -------------------------------------------------------------------------------- /test/graaflib/algorithm/coloring/welsh_powell_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace graaf::algorithm { 8 | 9 | template 10 | struct WelshPowellTest : public testing::Test { 11 | using graph_t = T; 12 | }; 13 | 14 | TYPED_TEST_SUITE(WelshPowellTest, 15 | utils::fixtures::minimal_undirected_graph_type); 16 | 17 | // Test case for an empty graph 18 | TYPED_TEST(WelshPowellTest, EmptyGraph) { 19 | // GIVEN 20 | using graph_t = typename TestFixture::graph_t; 21 | graph_t graph{}; 22 | 23 | // WHEN 24 | auto coloring = welsh_powell_coloring(graph); 25 | 26 | // THEN 27 | // Check if the obtained coloring is empty (no vertices to color) 28 | ASSERT_TRUE(coloring.empty()); 29 | } 30 | 31 | TYPED_TEST(WelshPowellTest, BasicGraphColoring) { 32 | // GIVEN 33 | using graph_t = typename TestFixture::graph_t; 34 | graph_t graph{}; 35 | 36 | // graph vertices 37 | const auto vertex_1{graph.add_vertex(10)}; 38 | const auto vertex_2{graph.add_vertex(20)}; 39 | const auto vertex_3{graph.add_vertex(30)}; 40 | 41 | // adding edges to our graph 42 | graph.add_edge(vertex_1, vertex_2, 1); 43 | graph.add_edge(vertex_2, vertex_3, 1); 44 | 45 | // WHEN 46 | auto coloring = welsh_powell_coloring(graph); 47 | 48 | // THEN 49 | std::unordered_map expected_coloring = { 50 | {0, 1}, {2, 1}, {1, 0}}; 51 | 52 | // Check if the obtained coloring matches the expected coloring 53 | ASSERT_EQ(coloring, expected_coloring); 54 | } 55 | 56 | TYPED_TEST(WelshPowellTest, GraphWithNoEdges) { 57 | // GIVEN 58 | using graph_t = typename TestFixture::graph_t; 59 | graph_t graph{}; 60 | 61 | // graph vertices 62 | const auto vertex_1{graph.add_vertex(10)}; 63 | const auto vertex_2{graph.add_vertex(20)}; 64 | const auto vertex_3{graph.add_vertex(30)}; 65 | const auto vertex_4{graph.add_vertex(40)}; 66 | 67 | // WHEN 68 | auto coloring = welsh_powell_coloring(graph); 69 | 70 | std::unordered_map expected_coloring = { 71 | {0, 0}, // Each vertex is assigned the same color 72 | {1, 0}, 73 | {2, 0}, 74 | {3, 0}}; 75 | 76 | // THEN 77 | // Check if the obtained coloring matches the expected coloring 78 | ASSERT_EQ(coloring, expected_coloring); 79 | } 80 | 81 | // Test with a complete graph 82 | TYPED_TEST(WelshPowellTest, CompleteGraph) { 83 | // GIVEN 84 | using graph_t = typename TestFixture::graph_t; 85 | graph_t graph{}; 86 | 87 | // graph vertices 88 | const auto vertex_1{graph.add_vertex(1)}; 89 | const auto vertex_2{graph.add_vertex(2)}; 90 | const auto vertex_3{graph.add_vertex(3)}; 91 | const auto vertex_4{graph.add_vertex(4)}; 92 | 93 | graph.add_edge(vertex_1, vertex_2, 1); 94 | graph.add_edge(vertex_1, vertex_3, 1); 95 | graph.add_edge(vertex_1, vertex_4, 1); 96 | graph.add_edge(vertex_2, vertex_3, 1); 97 | graph.add_edge(vertex_2, vertex_4, 1); 98 | graph.add_edge(vertex_3, vertex_4, 1); 99 | 100 | // WHEN 101 | auto coloring = welsh_powell_coloring(graph); 102 | 103 | // THEN 104 | std::unordered_map expected_coloring = { 105 | {0, 3}, {1, 2}, {2, 1}, {3, 0}}; 106 | 107 | // Check if the obtained coloring matches the expected coloring 108 | ASSERT_EQ(coloring, expected_coloring); 109 | } 110 | 111 | TYPED_TEST(WelshPowellTest, DisconnectedComponents) { 112 | // GIVEN 113 | using graph_t = typename TestFixture::graph_t; 114 | graph_t graph{}; 115 | 116 | // graph vertices 117 | const auto vertex_1{graph.add_vertex(10)}; 118 | const auto vertex_2{graph.add_vertex(20)}; 119 | const auto vertex_3{graph.add_vertex(30)}; 120 | const auto vertex_4{graph.add_vertex(40)}; 121 | const auto vertex_5{graph.add_vertex(50)}; 122 | 123 | // Create disconnected components 124 | graph.add_edge(vertex_1, vertex_2, 125 | 1); // Component 1: Vertex 1 and 2 are connected 126 | graph.add_edge(vertex_3, vertex_4, 127 | 1); // Component 2: Vertex 3 and 4 are 128 | // Component 3: connected Vertex 5 is disconnected 129 | // WHEN 130 | auto coloring = welsh_powell_coloring(graph); 131 | 132 | // THEN 133 | // Verify that for each edge, adjacent vertices have different colors 134 | for (const auto& [edge_id, edge] : graph.get_edges()) { 135 | const auto [u, v]{edge_id}; 136 | int color_u = coloring[u]; 137 | int color_v = coloring[v]; 138 | ASSERT_TRUE(color_u != color_v); 139 | } 140 | } 141 | 142 | } // namespace graaf::algorithm -------------------------------------------------------------------------------- /test/graaflib/algorithm/utils_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /** 5 | * Tests which miscellaneous utility functions contained 6 | * in utils.h should go here. Any test relating specifically 7 | * to the public graph interface should instead be located in 8 | * graph_test.cpp. 9 | */ 10 | 11 | namespace graaf { 12 | 13 | TEST(UtilsTest, Transpose) { 14 | // GIVEN 15 | using graph_t = directed_graph; 16 | graph_t graph{}; 17 | const auto vertex_id_1 = graph.add_vertex(1); 18 | const auto vertex_id_2 = graph.add_vertex(2); 19 | const auto vertex_id_3 = graph.add_vertex(3); 20 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 21 | graph.add_edge(vertex_id_2, vertex_id_3, 200); 22 | graph.add_edge(vertex_id_3, vertex_id_1, 300); 23 | 24 | // WHEN 25 | graph_t transposed_graph = get_transposed_graph(graph); 26 | 27 | // THEN 28 | EXPECT_EQ(get_weight(transposed_graph.get_edge(vertex_id_2, vertex_id_1)), 29 | 100); 30 | EXPECT_EQ(get_weight(transposed_graph.get_edge(vertex_id_3, vertex_id_2)), 31 | 200); 32 | EXPECT_EQ(get_weight(transposed_graph.get_edge(vertex_id_1, vertex_id_3)), 33 | 300); 34 | } 35 | 36 | } // namespace graaf -------------------------------------------------------------------------------- /test/graaflib/directed_graph_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace graaf { 5 | 6 | TEST(DirectedGraphTest, DirectedGraphIsNoUndirectedGraph) { 7 | // GIVEN - WHEN 8 | directed_graph graph{}; 9 | 10 | // THEN 11 | ASSERT_TRUE(graph.is_directed()); 12 | ASSERT_FALSE(graph.is_undirected()); 13 | } 14 | 15 | TEST(DirectedGraphTest, EdgeCount) { 16 | // GIVEN 17 | directed_graph graph{}; 18 | ASSERT_EQ(graph.edge_count(), 0); 19 | 20 | const auto vertex_id_1{graph.add_vertex(10)}; 21 | const auto vertex_id_2{graph.add_vertex(20)}; 22 | 23 | // WHEN 24 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 25 | 26 | // THEN 27 | ASSERT_EQ(graph.edge_count(), 1); 28 | ASSERT_TRUE(graph.has_edge(vertex_id_1, vertex_id_2)); 29 | ASSERT_FALSE(graph.has_edge(vertex_id_2, vertex_id_1)); 30 | ASSERT_EQ(get_weight(graph.get_edge(vertex_id_1, vertex_id_2)), 100); 31 | } 32 | 33 | TEST(DirectedGraphTest, GetNeighbors) { 34 | // GIVEN 35 | directed_graph graph{}; 36 | 37 | const auto vertex_id_1{graph.add_vertex(10)}; 38 | const auto vertex_id_2{graph.add_vertex(20)}; 39 | const auto vertex_id_3{graph.add_vertex(30)}; 40 | 41 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 42 | graph.add_edge(vertex_id_1, vertex_id_3, 200); 43 | 44 | // WHEN - THEN 45 | const auto neighbors_vertex_1{graph.get_neighbors(vertex_id_1)}; 46 | ASSERT_EQ(neighbors_vertex_1.size(), 2); 47 | ASSERT_TRUE(neighbors_vertex_1.contains(vertex_id_2)); 48 | ASSERT_TRUE(neighbors_vertex_1.contains(vertex_id_3)); 49 | 50 | // WHEN - THEN 51 | // The graph is directed so vertex 2 has no neighbors 52 | const auto neighbors_vertex_2{graph.get_neighbors(vertex_id_2)}; 53 | ASSERT_TRUE(neighbors_vertex_2.empty()); 54 | 55 | // WHEN - THEN 56 | // The graph is directed so vertex 3 has no neighbors 57 | const auto neighbors_vertex_3{graph.get_neighbors(vertex_id_3)}; 58 | ASSERT_TRUE(neighbors_vertex_3.empty()); 59 | } 60 | 61 | } // namespace graaf -------------------------------------------------------------------------------- /test/graaflib/graph_traits_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace graaf { 5 | 6 | /** 7 | * DIRECTED GRAPH 8 | */ 9 | 10 | TEST(GraphTraitsTest, DirectedGraphPrimitiveType) { 11 | // GIVEN - WHEN 12 | directed_graph graph{}; 13 | 14 | // THEN The graph is directed 15 | ASSERT_TRUE(graph.is_directed()); 16 | ASSERT_FALSE(graph.is_undirected()); 17 | } 18 | 19 | TEST(GraphTraitsTest, DirectedGraphNonPrimitiveType) { 20 | // GIVEN - WHEN 21 | struct my_edge {}; 22 | 23 | directed_graph graph{}; 24 | 25 | // THEN The graph is directed 26 | ASSERT_TRUE(graph.is_directed()); 27 | ASSERT_FALSE(graph.is_undirected()); 28 | } 29 | 30 | /** 31 | * UNDIRECTED GRAPH 32 | */ 33 | 34 | TEST(GraphTraitsTest, UndirectedGraphPrimitiveType) { 35 | // GIVEN - WHEN 36 | undirected_graph graph{}; 37 | 38 | // THEN The graph is undirected 39 | ASSERT_TRUE(graph.is_undirected()); 40 | ASSERT_FALSE(graph.is_directed()); 41 | } 42 | 43 | TEST(GraphTraitsTest, UndirectedGraphNonPrimitiveType) { 44 | // GIVEN - WHEN 45 | struct my_edge {}; 46 | 47 | undirected_graph graph{}; 48 | 49 | // THEN The graph is undirected 50 | ASSERT_TRUE(graph.is_undirected()); 51 | ASSERT_FALSE(graph.is_directed()); 52 | } 53 | 54 | } // namespace graaf -------------------------------------------------------------------------------- /test/graaflib/properties/vertex_properties_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace graaf::properties { 7 | 8 | TEST(DirectedGraphPropertiesTest, VertexOutDegree) { 9 | // GIVEN 10 | directed_graph graph{}; 11 | 12 | const auto vertex_id_1{graph.add_vertex(10)}; 13 | const auto vertex_id_2{graph.add_vertex(20)}; 14 | const auto vertex_id_3{graph.add_vertex(30)}; 15 | const auto vertex_id_4{graph.add_vertex(40)}; 16 | 17 | // WHEN 18 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 19 | graph.add_edge(vertex_id_2, vertex_id_3, 200); 20 | graph.add_edge(vertex_id_2, vertex_id_4, 300); 21 | 22 | // THEN 23 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_1), 1); 24 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_2), 2); 25 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_3), 0); 26 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_4), 0); 27 | } 28 | 29 | TEST(DirectedGraphPropertiesTest, VertexInDegree) { 30 | // GIVEN 31 | directed_graph graph{}; 32 | 33 | const auto vertex_id_1{graph.add_vertex(10)}; 34 | const auto vertex_id_2{graph.add_vertex(20)}; 35 | const auto vertex_id_3{graph.add_vertex(30)}; 36 | const auto vertex_id_4{graph.add_vertex(40)}; 37 | 38 | // WHEN 39 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 40 | graph.add_edge(vertex_id_1, vertex_id_3, 200); 41 | graph.add_edge(vertex_id_3, vertex_id_4, 300); 42 | graph.add_edge(vertex_id_2, vertex_id_4, 400); 43 | 44 | // THEN 45 | ASSERT_EQ(vertex_indegree(graph, vertex_id_1), 0); 46 | ASSERT_EQ(vertex_indegree(graph, vertex_id_2), 1); 47 | ASSERT_EQ(vertex_indegree(graph, vertex_id_3), 1); 48 | ASSERT_EQ(vertex_indegree(graph, vertex_id_4), 2); 49 | } 50 | 51 | TEST(DirectedGraphPropertiesTest, VertexDegree) { 52 | // GIVEN 53 | directed_graph graph{}; 54 | 55 | const auto vertex_id_1{graph.add_vertex(10)}; 56 | const auto vertex_id_2{graph.add_vertex(20)}; 57 | const auto vertex_id_3{graph.add_vertex(30)}; 58 | const auto vertex_id_4{graph.add_vertex(40)}; 59 | 60 | // WHEN 61 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 62 | graph.add_edge(vertex_id_1, vertex_id_3, 200); 63 | graph.add_edge(vertex_id_3, vertex_id_4, 300); 64 | graph.add_edge(vertex_id_2, vertex_id_4, 400); 65 | 66 | // THEN 67 | ASSERT_EQ(vertex_degree(graph, vertex_id_1), 2); 68 | ASSERT_EQ(vertex_degree(graph, vertex_id_2), 2); 69 | ASSERT_EQ(vertex_degree(graph, vertex_id_3), 2); 70 | ASSERT_EQ(vertex_degree(graph, vertex_id_4), 2); 71 | } 72 | 73 | TEST(UndirectedGraphPropertiesTest, VertexOutDegree) { 74 | // GIVEN 75 | undirected_graph graph{}; 76 | 77 | const auto vertex_id_1{graph.add_vertex(10)}; 78 | const auto vertex_id_2{graph.add_vertex(20)}; 79 | const auto vertex_id_3{graph.add_vertex(30)}; 80 | const auto vertex_id_4{graph.add_vertex(40)}; 81 | 82 | // WHEN 83 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 84 | graph.add_edge(vertex_id_2, vertex_id_3, 200); 85 | graph.add_edge(vertex_id_2, vertex_id_4, 300); 86 | 87 | // THEN 88 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_1), 1); 89 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_2), 3); 90 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_3), 1); 91 | ASSERT_EQ(vertex_outdegree(graph, vertex_id_4), 1); 92 | } 93 | 94 | TEST(UndirectedGraphPropertiesTest, VertexInDegree) { 95 | // GIVEN 96 | undirected_graph graph{}; 97 | 98 | const auto vertex_id_1{graph.add_vertex(10)}; 99 | const auto vertex_id_2{graph.add_vertex(20)}; 100 | const auto vertex_id_3{graph.add_vertex(30)}; 101 | const auto vertex_id_4{graph.add_vertex(40)}; 102 | 103 | // WHEN 104 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 105 | graph.add_edge(vertex_id_2, vertex_id_3, 200); 106 | graph.add_edge(vertex_id_2, vertex_id_4, 300); 107 | 108 | // THEN 109 | ASSERT_EQ(vertex_indegree(graph, vertex_id_1), 1); 110 | ASSERT_EQ(vertex_indegree(graph, vertex_id_2), 3); 111 | ASSERT_EQ(vertex_indegree(graph, vertex_id_3), 1); 112 | ASSERT_EQ(vertex_indegree(graph, vertex_id_4), 1); 113 | } 114 | 115 | TEST(UndirectedGraphPropertiesTest, VertexDegree) { 116 | // GIVEN 117 | undirected_graph graph{}; 118 | 119 | const auto vertex_id_1{graph.add_vertex(10)}; 120 | const auto vertex_id_2{graph.add_vertex(20)}; 121 | const auto vertex_id_3{graph.add_vertex(30)}; 122 | const auto vertex_id_4{graph.add_vertex(40)}; 123 | 124 | // WHEN 125 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 126 | graph.add_edge(vertex_id_2, vertex_id_3, 200); 127 | graph.add_edge(vertex_id_2, vertex_id_4, 300); 128 | 129 | // THEN 130 | ASSERT_EQ(vertex_degree(graph, vertex_id_1), 1); 131 | ASSERT_EQ(vertex_degree(graph, vertex_id_2), 3); 132 | ASSERT_EQ(vertex_degree(graph, vertex_id_3), 1); 133 | ASSERT_EQ(vertex_degree(graph, vertex_id_4), 1); 134 | } 135 | 136 | } // namespace graaf::properties 137 | -------------------------------------------------------------------------------- /test/graaflib/tree_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace graaf { 5 | 6 | TEST(TreeTest, CanConstructWithRootNode) { 7 | // GIVEN 8 | using tree_t = tree; 9 | constexpr const int ROOT_VAL{42}; 10 | 11 | // WHEN 12 | const tree_t tree{ROOT_VAL}; 13 | 14 | // THEN 15 | ASSERT_NE(tree.root(), nullptr); 16 | ASSERT_EQ(tree.root()->value, ROOT_VAL); 17 | ASSERT_EQ(tree.root()->parent, nullptr); 18 | ASSERT_EQ(tree.root()->children.size(), 0); 19 | } 20 | 21 | TEST(TreeTest, CanConstructWithChild) { 22 | // GIVEN 23 | using tree_t = tree; 24 | constexpr const int EDGE_VAL{33}; 25 | constexpr const int CHILD_VAL{42}; 26 | 27 | tree_t tree{42}; 28 | 29 | // WHEN 30 | const auto* child{tree.root()->add_child(EDGE_VAL, CHILD_VAL)}; 31 | 32 | // THEN 33 | const auto* root{tree.root()}; 34 | ASSERT_EQ(root->children.size(), 1); 35 | ASSERT_EQ(root->children.front().value, EDGE_VAL); 36 | ASSERT_EQ(root->children.front().child.get(), child); 37 | 38 | ASSERT_EQ(child->value, CHILD_VAL); 39 | ASSERT_EQ(child->parent, root); 40 | ASSERT_EQ(child->children.size(), 0); 41 | } 42 | 43 | } // namespace graaf 44 | -------------------------------------------------------------------------------- /test/graaflib/undirected_graph_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace graaf { 5 | 6 | TEST(UndirectedGraphTest, DirectedGraphIsNoUndirectedGraph) { 7 | // GIVEN - WHEN 8 | undirected_graph graph{}; 9 | 10 | // THEN 11 | ASSERT_FALSE(graph.is_directed()); 12 | ASSERT_TRUE(graph.is_undirected()); 13 | } 14 | 15 | TEST(UndirectedGraphTest, EdgeCount) { 16 | // GIVEN 17 | undirected_graph graph{}; 18 | ASSERT_EQ(graph.edge_count(), 0); 19 | 20 | const auto vertex_id_1{graph.add_vertex(10)}; 21 | const auto vertex_id_2{graph.add_vertex(20)}; 22 | 23 | // WHEN 24 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 25 | 26 | // THEN 27 | ASSERT_EQ(graph.edge_count(), 1); 28 | ASSERT_TRUE(graph.has_edge(vertex_id_1, vertex_id_2)); 29 | ASSERT_TRUE(graph.has_edge(vertex_id_2, vertex_id_1)); 30 | ASSERT_EQ(get_weight(graph.get_edge(vertex_id_1, vertex_id_2)), 100); 31 | } 32 | 33 | TEST(UndirectedGraphTest, GetNeighbors) { 34 | // GIVEN 35 | undirected_graph graph{}; 36 | 37 | const auto vertex_id_1{graph.add_vertex(10)}; 38 | const auto vertex_id_2{graph.add_vertex(20)}; 39 | const auto vertex_id_3{graph.add_vertex(30)}; 40 | 41 | graph.add_edge(vertex_id_1, vertex_id_2, 100); 42 | graph.add_edge(vertex_id_1, vertex_id_3, 200); 43 | 44 | // WHEN - THEN 45 | const auto neighbors_vertex_1{graph.get_neighbors(vertex_id_1)}; 46 | ASSERT_EQ(neighbors_vertex_1.size(), 2); 47 | ASSERT_TRUE(neighbors_vertex_1.contains(vertex_id_2)); 48 | ASSERT_TRUE(neighbors_vertex_1.contains(vertex_id_3)); 49 | 50 | // WHEN - THEN 51 | // The edges are undirected, so vertex 2 has a neighbor 52 | const auto neighbors_vertex_2{graph.get_neighbors(vertex_id_2)}; 53 | ASSERT_EQ(neighbors_vertex_2.size(), 1); 54 | ASSERT_TRUE(neighbors_vertex_2.contains(vertex_id_1)); 55 | 56 | // WHEN - THEN 57 | // The edges are undirected, so vertex 3 has a neighbor 58 | const auto neighbors_vertex_3{graph.get_neighbors(vertex_id_3)}; 59 | ASSERT_EQ(neighbors_vertex_3.size(), 1); 60 | ASSERT_TRUE(neighbors_vertex_3.contains(vertex_id_1)); 61 | } 62 | 63 | } // namespace graaf -------------------------------------------------------------------------------- /test/graaflib/weighted_graph_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace graaf { 9 | 10 | template 11 | struct WeightedGraphTest : public testing::Test { 12 | using graph_t = typename T::first_type; 13 | using edge_t = typename T::second_type; 14 | }; 15 | 16 | TYPED_TEST_SUITE(WeightedGraphTest, utils::fixtures::weighted_graph_types); 17 | 18 | TYPED_TEST(WeightedGraphTest, AddWeightedEdge) { 19 | // GIVEN 20 | using graph_t = typename TestFixture::graph_t; 21 | using edge_t = typename TestFixture::edge_t; 22 | using weight_t = decltype(get_weight(std::declval())); 23 | 24 | graph_t graph{}; 25 | 26 | const auto vertex_id_1{graph.add_vertex(10)}; 27 | const auto vertex_id_2{graph.add_vertex(20)}; 28 | 29 | // WHEN 30 | graph.add_edge(vertex_id_1, vertex_id_2, edge_t{static_cast(3)}); 31 | 32 | // THEN 33 | ASSERT_TRUE(graph.has_edge(vertex_id_1, vertex_id_2)); 34 | ASSERT_EQ(get_weight(graph.get_edge(vertex_id_1, vertex_id_2)), 35 | static_cast(3)); 36 | } 37 | 38 | template 39 | struct UnitWeightedGraphTest : public testing::Test { 40 | using graph_t = typename T::first_type; 41 | using edge_t = typename T::second_type; 42 | }; 43 | 44 | TYPED_TEST_SUITE(UnitWeightedGraphTest, 45 | utils::fixtures::unit_weighted_graph_types); 46 | 47 | TYPED_TEST(UnitWeightedGraphTest, AddUnitWeightedEdge) { 48 | // GIVEN 49 | using graph_t = typename TestFixture::graph_t; 50 | using edge_t = typename TestFixture::edge_t; 51 | using weight_t = decltype(get_weight(std::declval())); 52 | 53 | graph_t graph{}; 54 | 55 | const auto vertex_id_1{graph.add_vertex(10)}; 56 | const auto vertex_id_2{graph.add_vertex(20)}; 57 | 58 | // WHEN 59 | graph.add_edge(vertex_id_1, vertex_id_2, edge_t{}); 60 | 61 | // THEN 62 | ASSERT_TRUE(graph.has_edge(vertex_id_1, vertex_id_2)); 63 | 64 | // By default each edge has a unit weight 65 | ASSERT_EQ(get_weight(graph.get_edge(vertex_id_1, vertex_id_2)), 66 | static_cast(1)); 67 | } 68 | 69 | template 70 | struct UnweightedGraphTest : public testing::Test { 71 | using graph_t = typename T::first_type; 72 | using edge_t = typename T::second_type; 73 | }; 74 | 75 | TYPED_TEST_SUITE(UnweightedGraphTest, utils::fixtures::unweighted_graph_types); 76 | 77 | TYPED_TEST(UnweightedGraphTest, AddUnweightedEdge) { 78 | // GIVEN 79 | using graph_t = typename TestFixture::graph_t; 80 | using edge_t = typename TestFixture::edge_t; 81 | using weight_t = decltype(get_weight(std::declval())); 82 | 83 | graph_t graph{}; 84 | 85 | const auto vertex_id_1{graph.add_vertex(10)}; 86 | const auto vertex_id_2{graph.add_vertex(20)}; 87 | 88 | // WHEN 89 | graph.add_edge(vertex_id_1, vertex_id_2, edge_t{static_cast(42)}); 90 | 91 | // THEN 92 | ASSERT_TRUE(graph.has_edge(vertex_id_1, vertex_id_2)); 93 | 94 | // By default each edge has a unit weight 95 | ASSERT_EQ(graph.get_edge(vertex_id_1, vertex_id_2).val, 96 | static_cast(42)); 97 | } 98 | 99 | } // namespace graaf -------------------------------------------------------------------------------- /test/utils/scenarios/scenarios.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace graaf::utils::scenarios { 4 | 5 | template 6 | scenario create_tree_scenario() { 7 | std::vector vertex_ids{}; 8 | vertex_ids.reserve(5); 9 | 10 | GRAPH_T graph{}; 11 | 12 | vertex_ids.push_back(graph.add_vertex(10)); 13 | vertex_ids.push_back(graph.add_vertex(20)); 14 | vertex_ids.push_back(graph.add_vertex(30)); 15 | vertex_ids.push_back(graph.add_vertex(40)); 16 | vertex_ids.push_back(graph.add_vertex(50)); 17 | 18 | graph.add_edge(vertex_ids[0], vertex_ids[1], 100); 19 | graph.add_edge(vertex_ids[0], vertex_ids[2], 200); 20 | graph.add_edge(vertex_ids[2], vertex_ids[3], 300); 21 | graph.add_edge(vertex_ids[2], vertex_ids[4], 400); 22 | 23 | return {std::move(graph), std::move(vertex_ids)}; 24 | } 25 | 26 | template 27 | scenario create_simple_graph_scenario() { 28 | std::vector vertex_ids{}; 29 | vertex_ids.reserve(5); 30 | 31 | GRAPH_T graph{}; 32 | 33 | vertex_ids.push_back(graph.add_vertex(10)); 34 | vertex_ids.push_back(graph.add_vertex(20)); 35 | vertex_ids.push_back(graph.add_vertex(30)); 36 | vertex_ids.push_back(graph.add_vertex(40)); 37 | vertex_ids.push_back(graph.add_vertex(50)); 38 | 39 | graph.add_edge(vertex_ids[0], vertex_ids[1], 100); 40 | graph.add_edge(vertex_ids[1], vertex_ids[2], 200); 41 | graph.add_edge(vertex_ids[2], vertex_ids[0], 300); 42 | graph.add_edge(vertex_ids[2], vertex_ids[3], 400); 43 | graph.add_edge(vertex_ids[3], vertex_ids[4], 500); 44 | graph.add_edge(vertex_ids[2], vertex_ids[4], 600); 45 | 46 | return {std::move(graph), std::move(vertex_ids)}; 47 | } 48 | 49 | template 50 | scenario create_fully_connected_graph_scenario() { 51 | std::vector vertex_ids{}; 52 | vertex_ids.reserve(5); 53 | 54 | GRAPH_T graph{}; 55 | 56 | vertex_ids.push_back(graph.add_vertex(10)); 57 | vertex_ids.push_back(graph.add_vertex(20)); 58 | vertex_ids.push_back(graph.add_vertex(30)); 59 | vertex_ids.push_back(graph.add_vertex(40)); 60 | vertex_ids.push_back(graph.add_vertex(50)); 61 | 62 | graph.add_edge(vertex_ids[0], vertex_ids[1], 100); 63 | graph.add_edge(vertex_ids[0], vertex_ids[2], 200); 64 | graph.add_edge(vertex_ids[0], vertex_ids[3], 300); 65 | graph.add_edge(vertex_ids[0], vertex_ids[4], 400); 66 | graph.add_edge(vertex_ids[1], vertex_ids[2], 500); 67 | graph.add_edge(vertex_ids[1], vertex_ids[3], 600); 68 | graph.add_edge(vertex_ids[1], vertex_ids[4], 700); 69 | graph.add_edge(vertex_ids[2], vertex_ids[3], 800); 70 | graph.add_edge(vertex_ids[2], vertex_ids[4], 900); 71 | graph.add_edge(vertex_ids[3], vertex_ids[4], 1000); 72 | 73 | return {std::move(graph), std::move(vertex_ids)}; 74 | } 75 | 76 | template 77 | scenario create_disconnected_graph_scenario() { 78 | std::vector vertex_ids{}; 79 | vertex_ids.reserve(6); 80 | 81 | GRAPH_T graph{}; 82 | 83 | vertex_ids.push_back(graph.add_vertex(10)); 84 | vertex_ids.push_back(graph.add_vertex(20)); 85 | vertex_ids.push_back(graph.add_vertex(30)); 86 | 87 | vertex_ids.push_back(graph.add_vertex(40)); 88 | vertex_ids.push_back(graph.add_vertex(50)); 89 | vertex_ids.push_back(graph.add_vertex(60)); 90 | 91 | graph.add_edge(vertex_ids[0], vertex_ids[1], 100); 92 | graph.add_edge(vertex_ids[0], vertex_ids[2], 200); 93 | 94 | graph.add_edge(vertex_ids[3], vertex_ids[4], 300); 95 | graph.add_edge(vertex_ids[4], vertex_ids[5], 400); 96 | graph.add_edge(vertex_ids[5], vertex_ids[3], 500); 97 | 98 | return {std::move(graph), std::move(vertex_ids)}; 99 | } 100 | 101 | } // namespace graaf::utils::scenarios -------------------------------------------------------------------------------- /tools/benchmark_visualization.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | import seaborn as sns 6 | 7 | """ 8 | This script can be used to visualize benchmark results. The script assumes multiple benchmark 9 | results in csv files under the build directory. These results can be generated with the 10 | Graaf_perf executable: 11 | 12 | build/perf/Graaf_perf --benchmark_out=benchmark_results.csv --benchmark_out_format=csv 13 | """ 14 | 15 | 16 | def read_benchmark_files_into_df(dir=Path("../build"), skiprows=10): 17 | df = pd.DataFrame() 18 | for file in dir.glob("**/*.csv"): 19 | df_new = pd.read_csv(file, skiprows=skiprows) 20 | df_new["filename"] = Path(file).stem 21 | df = pd.concat([df, df_new], ignore_index=True) 22 | 23 | assert not df.empty, "Reading into df failed. Check dir and skiprows parameters!" 24 | 25 | df[["bm_name", "variable"]] = df["name"].str.split(pat="/", n=1, expand=True) 26 | df["variable"] = df["variable"].astype(int) 27 | df["time (s)"] = df["real_time"].apply(lambda x: x * 1e-9) 28 | df["ID"] = df["filename"] + " - " + df["bm_name"] 29 | return df 30 | 31 | 32 | def plot_benchmarks_from_df(df, ax=None, save_plot=False): 33 | if ax is None: 34 | _, ax = plt.subplots(figsize=(7, 5)) 35 | ax.set(xscale="log", xlabel="No. of Additions", ylabel="Runtime (s)") 36 | 37 | sns.lineplot(df, x="variable", y="time (s)", hue="ID", legend="full", ax=ax) 38 | plt.savefig("plot.png") if save_plot else plt.show() 39 | 40 | 41 | if __name__ == "__main__": 42 | df = read_benchmark_files_into_df() 43 | plot_benchmarks_from_df(df) 44 | -------------------------------------------------------------------------------- /tools/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib>=3.7.2 2 | pandas>=2.0.3 3 | seaborn>=0.12.2 --------------------------------------------------------------------------------