├── .github └── workflows │ ├── ci.yml │ ├── multi-os.yml │ └── rustdoc.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── graph.rs ├── metis-sys ├── Cargo.toml ├── build.rs ├── gen │ └── bindings.rs ├── src │ └── lib.rs └── wrapper.h └── src ├── lib.rs └── option.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | name: Test Suite 15 | runs-on: ubuntu-22.04 16 | strategy: 17 | matrix: 18 | rust: 19 | - stable 20 | - 1.67.0 21 | - nightly 22 | features: 23 | - vendored 24 | - use-system 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | submodules: recursive 29 | - uses: dtolnay/rust-toolchain@master 30 | with: 31 | components: clippy, rustfmt 32 | toolchain: ${{ matrix.rust }} 33 | - run: sudo apt-get -y install libmetis-dev libclang-dev 34 | if: matrix.features == 'use-system' 35 | - name: Check Format 36 | run: cargo fmt -- --check 37 | - name: Run Check 38 | run: cargo check --features ${{ matrix.features }} --no-default-features 39 | - name: Run Clippy 40 | run: cargo clippy --features ${{ matrix.features }} --no-default-features 41 | - name: Run Tests 42 | run: cargo test --features ${{ matrix.features }} --no-default-features --all 43 | -------------------------------------------------------------------------------- /.github/workflows/multi-os.yml: -------------------------------------------------------------------------------- 1 | name: Multiple OS 2 | # Test the default configuration on multiple operating systems 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build_matrix: 12 | name: Run tests for ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | name: [linux, windows, macos] 17 | include: 18 | - name: linux 19 | os: ubuntu-latest 20 | - name: windows 21 | os: windows-latest 22 | - name: macos 23 | os: macos-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | submodules: recursive 28 | - uses: dtolnay/rust-toolchain@master 29 | with: 30 | toolchain: stable 31 | - name: Run Build 32 | run: cargo build 33 | - name: Run Tests 34 | run: cargo test --all 35 | -------------------------------------------------------------------------------- /.github/workflows/rustdoc.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | CARGO_INCREMENTAL: 0 10 | CARGO_NET_RETRY: 10 11 | RUSTFLAGS: "-D warnings" 12 | RUSTUP_MAX_RETRIES: 10 13 | 14 | jobs: 15 | rustdoc: 16 | runs-on: ubuntu-22.04 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | profile: minimal 26 | override: true 27 | components: rustfmt, rust-src 28 | - uses: actions-rs/cargo@v1 29 | with: 30 | command: doc 31 | args: --all --no-deps 32 | - name: Deploy Docs 33 | uses: peaceiris/actions-gh-pages@v3 34 | with: 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | publish_branch: gh-pages 37 | publish_dir: ./target/doc 38 | force_orphan: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .* 3 | cargo.lock 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/GKlib"] 2 | path = metis-sys/vendor/GKlib 3 | url = https://github.com/KarypisLab/GKlib 4 | [submodule "vendor/metis"] 5 | path = metis-sys/vendor/metis 6 | url = https://github.com/KarypisLab/METIS 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.2.2 (2024-10-28) 4 | 5 | [metis-sys-0.2.1...0.2.2](https://github.com/LIHPC-Computational-Geometry/metis-rs/compare/metis-0.2.0...metis-0.2.1) 6 | 7 | ### Fixed 8 | 9 | - Do not override compiler flags when `force-optimize-vendor` [#33](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/33) 10 | 11 | 12 | ### Documentation 13 | 14 | - Fix links in the README.md [#34](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/34) 15 | 16 | 17 | ### Contributors 18 | 19 | Thanks to all contributors to this release: 20 | - @cedricchevalier19 21 | - @Firestar9 22 | - @imrn99 23 | - @JMS55 24 | 25 | ## Version 0.2.1 (2024-03-10) 26 | 27 | [metis-sys-0.2.0...0.2.1](https://github.com/LIHPC-Computational-Geometry/metis-rs/compare/metis-0.2.0...metis-0.2.1) 28 | 29 | ### Added 30 | 31 | - `force-optimize-vendor` feature for `metis-sys` to force builtin metis to be compiled as optimized, even for debug or 32 | dev profiles [#31](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/31) 33 | 34 | ### Fixed 35 | 36 | - move `vendor` library in `metis-sys` [#29](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/29) 37 | 38 | ## Version 0.2.0 (2024-03-06) 39 | 40 | [metis-sys-0.1.2...0.2.0](https://github.com/LIHPC-Computational-Geometry/metis-rs/compare/metis-0.1.2...metis-0.2.0) 41 | 42 | ### Added 43 | 44 | - Builtin metis with the new `vendored` feature, enabled by 45 | default [#16](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/16) 46 | - Convert from sprs matrices [#10](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/10) 47 | - Add unchecked constructors [#13](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/13) 48 | 49 | ### Changed 50 | 51 | - Remove mutability requirement on input from public facing 52 | API [#18](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/18) 53 | - Remove numbering feature, now only Rust (or C) 0-based arrays are 54 | supported [#13](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/13) 55 | 56 | ### Documentation 57 | 58 | - Better documentation from metis user guide [#14](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/14) 59 | - Improve examples to use `?` instead of `unwrap` [#9](https://github.com/LIHPC-Computational-Geometry/metis-rs/pull/9) 60 | 61 | ### Contributors 62 | 63 | Thanks to all contributors to this release: 64 | 65 | - @cedricchevalier19 66 | - @gfaster 67 | - @hhirtz 68 | - @oisyn 69 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [".", "metis-sys"] 3 | 4 | [package] 5 | name = "metis" 6 | version = "0.2.2" 7 | authors = ["Hubert Hirtz ", "Cedric Chevalier "] 8 | edition = "2021" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/LIHPC-Computational-Geometry/metis-rs" 11 | description = "Idiomatic wrapper for METIS, the serial graph partitioner and fill-reducing matrix orderer" 12 | categories = ["api-bindings", "mathematics"] 13 | keywords = ["graph", "mesh", "matrix", "partitioning", "ordering"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [features] 18 | default = ["metis-sys/default"] 19 | 20 | # Build and statically link to METIS and GKLib. 21 | vendored = ["metis-sys/vendored"] 22 | 23 | # Use existing METIS install and links dynamically to it. 24 | use-system = ["metis-sys/use-system"] 25 | 26 | [dependencies] 27 | metis-sys = { version = "0.3", path = "metis-sys", default-features = false } 28 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 CEA and Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 CEA and Contributors 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # metis-rs 2 | 3 | **metis-rs** is a Rust library providing idiomatic bindings to [libmetis][METIS_GH], a library for graph and mesh 4 | partitioning. It is made to be used with Rust version 1.67.0 or above. 5 | 6 | ## Getting Started 7 | 8 | Library released on [crates.io](https://crates.io/crates/metis). To use it, add the following to your `Cargo.toml`: 9 | 10 | ```toml 11 | [dependencies] 12 | metis = "0.2" 13 | ``` 14 | 15 | The list of available versions and a change log are available in the [CHANGELOG.md](CHANGELOG.md) file. 16 | 17 | ## Features 18 | 19 | ### Use of Vendored Feature 20 | 21 | The `vendored` feature enables metis-rs to build METIS from source and link to it statically. If not enabled, metis-rs 22 | looks for an existing installation and links to it dynamically. 23 | 24 | ### Use of System-wide Feature 25 | 26 | The `use-system` feature enables metis-rs to use the system-wide installation of METIS. If not enabled, metis-rs will 27 | refer to its own version of METIS. 28 | 29 | Please note, `vendored` and `use-system` features are mutually exclusive. 30 | 31 | ## Guidance for non-standard METIS installations 32 | 33 | If you enabled the `use-system` feature and METIS is installed in a non-standard location, you must set the following 34 | environment variables: 35 | ```bash 36 | export METISDIR=path/to/your/metis/installation 37 | export CPATH="$METISDIR/include" 38 | export RUSTFLAGS="-L$METISDIR/lib" 39 | ``` 40 | 41 | `$METISDIR` must point to a directory containing both `lib/` and `include/` directories with METIS's shared libraries and headers, respectively. 42 | 43 | ## Building the documentation 44 | 45 | To build the documentation, especially if METIS is installed in a non-standard location, set the `RUSTDOCFLAGS` environment variable: 46 | 47 | ```bash 48 | export RUSTDOCFLAGS="-L$METISDIR/lib" 49 | ``` 50 | Then the following command will generate and open the documentation: 51 | ```bash 52 | cargo doc --no-deps --open 53 | ``` 54 | 55 | ## License 56 | 57 | > [!IMPORTANT] 58 | > Our licensing policy applies only to the rust crate `metis-rs`, not the original METIS library. 59 | > 60 | > For information about METIS' licensing policy, refer to the original library's [repository][METIS_GH]. 61 | 62 | metis-rs is distributed under the terms of both the MIT license and the Apache License (Version 2.0). Refer to `LICENSE-APACHE` and `LICENSE-MIT` for more details. 63 | 64 | ### Note on the `vendored` feature 65 | 66 | metis-rs comes with its `vendored` feature enabled by default. With this feature enabled, the original METIS code is compiled and is installed along metis-rs. 67 | Please note that METIS code is **always** under Apache-2 license. 68 | 69 | [METIS]: http://glaros.dtc.umn.edu/gkhome/metis/metis/overview 70 | [METIS_GH]: https://github.com/KarypisLab/METIS 71 | -------------------------------------------------------------------------------- /examples/graph.rs: -------------------------------------------------------------------------------- 1 | use metis::Graph; 2 | 3 | fn main() -> Result<(), metis::Error> { 4 | let xadj = &[0, 2, 5, 8, 11, 13, 16, 20, 24, 28, 31, 33, 36, 39, 42, 44]; 5 | #[rustfmt::skip] 6 | let adjncy = &[ 7 | 1, 5, 8 | 0, 2, 6, 9 | 1, 3, 7, 10 | 2, 4, 8, 11 | 3, 9, 12 | 0, 6, 10, 13 | 1, 5, 7, 11, 14 | 2, 6, 8, 12, 15 | 3, 7, 9, 13, 16 | 4, 8, 14, 17 | 5, 11, 18 | 6, 10, 12, 19 | 7, 11, 13, 20 | 8, 12, 14, 21 | 9, 13, 22 | ]; 23 | let mut part = vec![0x00; 15]; 24 | Graph::new(1, 2, xadj, adjncy)?.part_recursive(&mut part)?; 25 | println!("{:?}", part); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /metis-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "metis-sys" 3 | version = "0.3.2" 4 | authors = ["Hubert Hirtz ", "Cedric Chevalier "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/LIHPC-Computational-Geometry/metis-rs" 8 | description = "Raw FFI to METIS, the serial graph partitioner and fill-reducing matrix orderer" 9 | readme = "../README.md" 10 | categories = ["external-ffi-bindings", "mathematics"] 11 | keywords = ["graph", "mesh", "matrix", "partitioning", "ordering"] 12 | 13 | 14 | [features] 15 | default = ["vendored", "force-optimize-vendor"] 16 | 17 | # Build and statically link to METIS and GKLib. 18 | vendored = ["dep:cc"] 19 | 20 | # Use existing METIS install and links dynamically to it. 21 | use-system = ["bindgen"] 22 | 23 | # Regenerate bindings in metis-sys/gen/bindings.rs from METIS in the "vendor" 24 | # directory. Also enables "vendored". 25 | generate-bindings = ["vendored", "bindgen"] 26 | 27 | # Force Metis to be optimized and to not follow the current profile for Rust 28 | # Therefore, debug or dev build lead to correct performance. 29 | force-optimize-vendor = ["vendored"] 30 | 31 | [build-dependencies] 32 | bindgen = { version = "0.69", default-features = false, features = ["runtime"], optional = true } 33 | cc = { version = "1", features = ["parallel"], optional = true } 34 | -------------------------------------------------------------------------------- /metis-sys/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(feature = "vendored"), not(feature = "use-system")))] 2 | compile_error!(r#"either "use-system" or "vendored" must be enabled for `metis-sys`"#); 3 | 4 | #[cfg(feature = "vendored")] 5 | const IDX_SIZE: usize = 32; 6 | 7 | #[cfg(feature = "vendored")] 8 | const REAL_SIZE: usize = 32; 9 | 10 | #[cfg(feature = "vendored")] 11 | fn build_lib() { 12 | use std::env; 13 | use std::path::PathBuf; 14 | 15 | let vendor = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("vendor"); 16 | println!("cargo:rerun-if-changed={}", vendor.display()); 17 | 18 | let mut build = cc::Build::new(); 19 | build 20 | .define("IDXTYPEWIDTH", Some(IDX_SIZE.to_string().as_str())) 21 | .define("REALTYPEWIDTH", Some(REAL_SIZE.to_string().as_str())) 22 | .include(vendor.join("metis/include")); 23 | 24 | fn add_sources(build: &mut cc::Build, root: PathBuf, files: &[&str]) { 25 | build.files(files.iter().map(|src| root.join(src))); 26 | build.include(root); 27 | } 28 | 29 | add_sources( 30 | &mut build, 31 | vendor.join("metis/libmetis"), 32 | &[ 33 | "auxapi.c", 34 | "balance.c", 35 | "bucketsort.c", 36 | "checkgraph.c", 37 | "coarsen.c", 38 | "compress.c", 39 | "contig.c", 40 | "debug.c", 41 | "fm.c", 42 | "fortran.c", 43 | "frename.c", 44 | "gklib.c", 45 | "graph.c", 46 | "initpart.c", 47 | "kmetis.c", 48 | "kwayfm.c", 49 | "kwayrefine.c", 50 | "mcutil.c", 51 | "mesh.c", 52 | "meshpart.c", 53 | "minconn.c", 54 | "mincover.c", 55 | "mmd.c", 56 | "ometis.c", 57 | "options.c", 58 | "parmetis.c", 59 | "pmetis.c", 60 | "refine.c", 61 | "separator.c", 62 | "sfm.c", 63 | "srefine.c", 64 | "stat.c", 65 | "timing.c", 66 | "util.c", 67 | "wspace.c", 68 | ], 69 | ); 70 | 71 | add_sources( 72 | &mut build, 73 | vendor.join("GKlib"), 74 | &[ 75 | "b64.c", 76 | "blas.c", 77 | "cache.c", 78 | "csr.c", 79 | "error.c", 80 | "evaluate.c", 81 | "fkvkselect.c", 82 | "fs.c", 83 | "getopt.c", 84 | "gk_util.c", 85 | "gkregex.c", 86 | "graph.c", 87 | "htable.c", 88 | "io.c", 89 | "itemsets.c", 90 | "mcore.c", 91 | "memory.c", 92 | "pqueue.c", 93 | "random.c", 94 | "rw.c", 95 | "seq.c", 96 | "sort.c", 97 | "string.c", 98 | "timers.c", 99 | "tokenizer.c", 100 | ], 101 | ); 102 | 103 | let target = env::var("TARGET").unwrap(); 104 | 105 | if target.contains("windows") { 106 | add_sources(&mut build, vendor.join("GKlib/win32"), &["adapt.c"]); 107 | 108 | build 109 | .define("USE_GKREGEX", None) 110 | .define("WIN32", None) 111 | .define("__thread", Some("__declspec(thread)")); 112 | 113 | if target.contains("msvc") { 114 | build 115 | .define("MSC", None) 116 | .define("_CRT_SECURE_NO_WARNINGS", None); 117 | 118 | // force inclusion of math.h to make sure INFINITY is defined before gk_arch.h is parsed 119 | build.flag("/FImath.h"); 120 | } 121 | } else if target.contains("linux") { 122 | build 123 | .define("LINUX", None) 124 | .define("_FILE_OFFSET_BITS", Some("64")); 125 | } else if target.contains("apple") { 126 | build.define("MACOS", None); 127 | } 128 | 129 | #[cfg(any(not(debug_assertions), feature = "force-optimize-vendor"))] 130 | build.define("NDEBUG", None).define("NDEBUG2", None); 131 | 132 | // METIS triggers an infinite amount of warnings and showing them to users 133 | // downstream does not really help. 134 | build.warnings(false); 135 | 136 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 137 | let lib_dir = out_dir.join("lib"); 138 | 139 | build.out_dir(&lib_dir); 140 | build.compile("metis"); 141 | 142 | println!("cargo:rustc-link-search=native={}", lib_dir.display()); 143 | println!("cargo:rustc-link-lib=static=metis"); 144 | println!("cargo:lib={}", lib_dir.display()); 145 | println!("cargo:out={}", out_dir.display()); 146 | } 147 | 148 | #[cfg(not(feature = "vendored"))] 149 | fn build_lib() { 150 | println!("cargo:rustc-link-lib=metis"); 151 | } 152 | 153 | // Always generate bindings when running from a locally installed METIS library. 154 | // When building directly from source (feature = "vendored"), only regenerate 155 | // bindings on command (feature = "generate-bindings"). 156 | #[cfg(all( 157 | feature = "use-system", 158 | any(not(feature = "vendored"), feature = "generate-bindings") 159 | ))] 160 | fn generate_bindings() { 161 | use std::env; 162 | use std::path::PathBuf; 163 | 164 | #[cfg(feature = "vendored")] 165 | let builder = bindgen::builder() 166 | .clang_arg(format!("-DIDXTYPEWIDTH={}", IDX_SIZE)) 167 | .clang_arg(format!("-DREALTYPEWIDTH={}", REAL_SIZE)) 168 | .header("../vendor/metis/include/metis.h"); 169 | 170 | #[cfg(not(feature = "vendored"))] 171 | let builder = bindgen::builder().header("wrapper.h"); 172 | 173 | println!("cargo:rerun-if-changed=wrapper.h"); 174 | 175 | let bindings = builder 176 | .allowlist_function("METIS_.*") 177 | .allowlist_type("idx_t") 178 | .allowlist_type("real_t") 179 | .allowlist_type("rstatus_et") 180 | .allowlist_type("m.*_et") 181 | .allowlist_var("METIS_.*") 182 | .generate() 183 | .unwrap_or_else(|err| { 184 | eprintln!("Failed to generate bindings to METIS: {err}"); 185 | std::process::exit(1); 186 | }); 187 | 188 | let out_path = if cfg!(feature = "vendored") { 189 | PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("gen/bindings.rs") 190 | } else { 191 | PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs") 192 | }; 193 | 194 | bindings.write_to_file(&out_path).unwrap_or_else(|err| { 195 | eprintln!( 196 | "Failed to write METIS bindings to {:?}: {}", 197 | out_path.display(), 198 | err, 199 | ); 200 | std::process::exit(1); 201 | }); 202 | } 203 | 204 | #[cfg(not(all( 205 | feature = "use-system", 206 | any(not(feature = "vendored"), feature = "generate-bindings") 207 | )))] 208 | fn generate_bindings() {} 209 | 210 | fn main() { 211 | build_lib(); 212 | generate_bindings(); 213 | } 214 | -------------------------------------------------------------------------------- /metis-sys/gen/bindings.rs: -------------------------------------------------------------------------------- 1 | /* automatically generated by rust-bindgen 0.66.1 */ 2 | 3 | pub const METIS_VER_MAJOR: u32 = 5; 4 | pub const METIS_VER_MINOR: u32 = 2; 5 | pub const METIS_VER_SUBMINOR: u32 = 1; 6 | pub const METIS_NOPTIONS: u32 = 40; 7 | pub type idx_t = i32; 8 | pub type real_t = f32; 9 | extern "C" { 10 | pub fn METIS_PartGraphRecursive( 11 | nvtxs: *mut idx_t, 12 | ncon: *mut idx_t, 13 | xadj: *mut idx_t, 14 | adjncy: *mut idx_t, 15 | vwgt: *mut idx_t, 16 | vsize: *mut idx_t, 17 | adjwgt: *mut idx_t, 18 | nparts: *mut idx_t, 19 | tpwgts: *mut real_t, 20 | ubvec: *mut real_t, 21 | options: *mut idx_t, 22 | edgecut: *mut idx_t, 23 | part: *mut idx_t, 24 | ) -> ::std::os::raw::c_int; 25 | } 26 | extern "C" { 27 | pub fn METIS_PartGraphKway( 28 | nvtxs: *mut idx_t, 29 | ncon: *mut idx_t, 30 | xadj: *mut idx_t, 31 | adjncy: *mut idx_t, 32 | vwgt: *mut idx_t, 33 | vsize: *mut idx_t, 34 | adjwgt: *mut idx_t, 35 | nparts: *mut idx_t, 36 | tpwgts: *mut real_t, 37 | ubvec: *mut real_t, 38 | options: *mut idx_t, 39 | edgecut: *mut idx_t, 40 | part: *mut idx_t, 41 | ) -> ::std::os::raw::c_int; 42 | } 43 | extern "C" { 44 | pub fn METIS_MeshToDual( 45 | ne: *mut idx_t, 46 | nn: *mut idx_t, 47 | eptr: *mut idx_t, 48 | eind: *mut idx_t, 49 | ncommon: *mut idx_t, 50 | numflag: *mut idx_t, 51 | r_xadj: *mut *mut idx_t, 52 | r_adjncy: *mut *mut idx_t, 53 | ) -> ::std::os::raw::c_int; 54 | } 55 | extern "C" { 56 | pub fn METIS_MeshToNodal( 57 | ne: *mut idx_t, 58 | nn: *mut idx_t, 59 | eptr: *mut idx_t, 60 | eind: *mut idx_t, 61 | numflag: *mut idx_t, 62 | r_xadj: *mut *mut idx_t, 63 | r_adjncy: *mut *mut idx_t, 64 | ) -> ::std::os::raw::c_int; 65 | } 66 | extern "C" { 67 | pub fn METIS_PartMeshNodal( 68 | ne: *mut idx_t, 69 | nn: *mut idx_t, 70 | eptr: *mut idx_t, 71 | eind: *mut idx_t, 72 | vwgt: *mut idx_t, 73 | vsize: *mut idx_t, 74 | nparts: *mut idx_t, 75 | tpwgts: *mut real_t, 76 | options: *mut idx_t, 77 | objval: *mut idx_t, 78 | epart: *mut idx_t, 79 | npart: *mut idx_t, 80 | ) -> ::std::os::raw::c_int; 81 | } 82 | extern "C" { 83 | pub fn METIS_PartMeshDual( 84 | ne: *mut idx_t, 85 | nn: *mut idx_t, 86 | eptr: *mut idx_t, 87 | eind: *mut idx_t, 88 | vwgt: *mut idx_t, 89 | vsize: *mut idx_t, 90 | ncommon: *mut idx_t, 91 | nparts: *mut idx_t, 92 | tpwgts: *mut real_t, 93 | options: *mut idx_t, 94 | objval: *mut idx_t, 95 | epart: *mut idx_t, 96 | npart: *mut idx_t, 97 | ) -> ::std::os::raw::c_int; 98 | } 99 | extern "C" { 100 | pub fn METIS_NodeND( 101 | nvtxs: *mut idx_t, 102 | xadj: *mut idx_t, 103 | adjncy: *mut idx_t, 104 | vwgt: *mut idx_t, 105 | options: *mut idx_t, 106 | perm: *mut idx_t, 107 | iperm: *mut idx_t, 108 | ) -> ::std::os::raw::c_int; 109 | } 110 | extern "C" { 111 | pub fn METIS_Free(ptr: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int; 112 | } 113 | extern "C" { 114 | pub fn METIS_SetDefaultOptions(options: *mut idx_t) -> ::std::os::raw::c_int; 115 | } 116 | extern "C" { 117 | pub fn METIS_NodeNDP( 118 | nvtxs: idx_t, 119 | xadj: *mut idx_t, 120 | adjncy: *mut idx_t, 121 | vwgt: *mut idx_t, 122 | npes: idx_t, 123 | options: *mut idx_t, 124 | perm: *mut idx_t, 125 | iperm: *mut idx_t, 126 | sizes: *mut idx_t, 127 | ) -> ::std::os::raw::c_int; 128 | } 129 | extern "C" { 130 | pub fn METIS_ComputeVertexSeparator( 131 | nvtxs: *mut idx_t, 132 | xadj: *mut idx_t, 133 | adjncy: *mut idx_t, 134 | vwgt: *mut idx_t, 135 | options: *mut idx_t, 136 | sepsize: *mut idx_t, 137 | part: *mut idx_t, 138 | ) -> ::std::os::raw::c_int; 139 | } 140 | extern "C" { 141 | pub fn METIS_NodeRefine( 142 | nvtxs: idx_t, 143 | xadj: *mut idx_t, 144 | vwgt: *mut idx_t, 145 | adjncy: *mut idx_t, 146 | where_: *mut idx_t, 147 | hmarker: *mut idx_t, 148 | ubfactor: real_t, 149 | ) -> ::std::os::raw::c_int; 150 | } 151 | extern "C" { 152 | pub fn METIS_CacheFriendlyReordering( 153 | nvtxs: idx_t, 154 | xadj: *mut idx_t, 155 | adjncy: *mut idx_t, 156 | part: *mut idx_t, 157 | old2new: *mut idx_t, 158 | ) -> ::std::os::raw::c_int; 159 | } 160 | #[doc = "< Returned normally"] 161 | pub const rstatus_et_METIS_OK: rstatus_et = 1; 162 | #[doc = "< Returned due to erroneous inputs and/or options"] 163 | pub const rstatus_et_METIS_ERROR_INPUT: rstatus_et = -2; 164 | #[doc = "< Returned due to insufficient memory"] 165 | pub const rstatus_et_METIS_ERROR_MEMORY: rstatus_et = -3; 166 | #[doc = "< Some other errors"] 167 | pub const rstatus_et_METIS_ERROR: rstatus_et = -4; 168 | #[doc = " Return codes"] 169 | pub type rstatus_et = ::std::os::raw::c_int; 170 | pub const moptype_et_METIS_OP_PMETIS: moptype_et = 0; 171 | pub const moptype_et_METIS_OP_KMETIS: moptype_et = 1; 172 | pub const moptype_et_METIS_OP_OMETIS: moptype_et = 2; 173 | #[doc = " Operation type codes"] 174 | pub type moptype_et = ::std::os::raw::c_int; 175 | pub const moptions_et_METIS_OPTION_PTYPE: moptions_et = 0; 176 | pub const moptions_et_METIS_OPTION_OBJTYPE: moptions_et = 1; 177 | pub const moptions_et_METIS_OPTION_CTYPE: moptions_et = 2; 178 | pub const moptions_et_METIS_OPTION_IPTYPE: moptions_et = 3; 179 | pub const moptions_et_METIS_OPTION_RTYPE: moptions_et = 4; 180 | pub const moptions_et_METIS_OPTION_DBGLVL: moptions_et = 5; 181 | pub const moptions_et_METIS_OPTION_NIPARTS: moptions_et = 6; 182 | pub const moptions_et_METIS_OPTION_NITER: moptions_et = 7; 183 | pub const moptions_et_METIS_OPTION_NCUTS: moptions_et = 8; 184 | pub const moptions_et_METIS_OPTION_SEED: moptions_et = 9; 185 | pub const moptions_et_METIS_OPTION_ONDISK: moptions_et = 10; 186 | pub const moptions_et_METIS_OPTION_MINCONN: moptions_et = 11; 187 | pub const moptions_et_METIS_OPTION_CONTIG: moptions_et = 12; 188 | pub const moptions_et_METIS_OPTION_COMPRESS: moptions_et = 13; 189 | pub const moptions_et_METIS_OPTION_CCORDER: moptions_et = 14; 190 | pub const moptions_et_METIS_OPTION_PFACTOR: moptions_et = 15; 191 | pub const moptions_et_METIS_OPTION_NSEPS: moptions_et = 16; 192 | pub const moptions_et_METIS_OPTION_UFACTOR: moptions_et = 17; 193 | pub const moptions_et_METIS_OPTION_NUMBERING: moptions_et = 18; 194 | pub const moptions_et_METIS_OPTION_DROPEDGES: moptions_et = 19; 195 | pub const moptions_et_METIS_OPTION_NO2HOP: moptions_et = 20; 196 | pub const moptions_et_METIS_OPTION_TWOHOP: moptions_et = 21; 197 | pub const moptions_et_METIS_OPTION_FAST: moptions_et = 22; 198 | pub const moptions_et_METIS_OPTION_HELP: moptions_et = 23; 199 | pub const moptions_et_METIS_OPTION_TPWGTS: moptions_et = 24; 200 | pub const moptions_et_METIS_OPTION_NCOMMON: moptions_et = 25; 201 | pub const moptions_et_METIS_OPTION_NOOUTPUT: moptions_et = 26; 202 | pub const moptions_et_METIS_OPTION_BALANCE: moptions_et = 27; 203 | pub const moptions_et_METIS_OPTION_GTYPE: moptions_et = 28; 204 | pub const moptions_et_METIS_OPTION_UBVEC: moptions_et = 29; 205 | #[doc = " Options codes (i.e., options[])"] 206 | pub type moptions_et = ::std::os::raw::c_int; 207 | pub const mptype_et_METIS_PTYPE_RB: mptype_et = 0; 208 | pub const mptype_et_METIS_PTYPE_KWAY: mptype_et = 1; 209 | #[doc = " Partitioning Schemes"] 210 | pub type mptype_et = ::std::os::raw::c_int; 211 | pub const mgtype_et_METIS_GTYPE_DUAL: mgtype_et = 0; 212 | pub const mgtype_et_METIS_GTYPE_NODAL: mgtype_et = 1; 213 | #[doc = " Graph types for meshes"] 214 | pub type mgtype_et = ::std::os::raw::c_int; 215 | pub const mctype_et_METIS_CTYPE_RM: mctype_et = 0; 216 | pub const mctype_et_METIS_CTYPE_SHEM: mctype_et = 1; 217 | #[doc = " Coarsening Schemes"] 218 | pub type mctype_et = ::std::os::raw::c_int; 219 | pub const miptype_et_METIS_IPTYPE_GROW: miptype_et = 0; 220 | pub const miptype_et_METIS_IPTYPE_RANDOM: miptype_et = 1; 221 | pub const miptype_et_METIS_IPTYPE_EDGE: miptype_et = 2; 222 | pub const miptype_et_METIS_IPTYPE_NODE: miptype_et = 3; 223 | pub const miptype_et_METIS_IPTYPE_METISRB: miptype_et = 4; 224 | #[doc = " Initial partitioning schemes"] 225 | pub type miptype_et = ::std::os::raw::c_int; 226 | pub const mrtype_et_METIS_RTYPE_FM: mrtype_et = 0; 227 | pub const mrtype_et_METIS_RTYPE_GREEDY: mrtype_et = 1; 228 | pub const mrtype_et_METIS_RTYPE_SEP2SIDED: mrtype_et = 2; 229 | pub const mrtype_et_METIS_RTYPE_SEP1SIDED: mrtype_et = 3; 230 | #[doc = " Refinement schemes"] 231 | pub type mrtype_et = ::std::os::raw::c_int; 232 | #[doc = "< Shows various diagnostic messages"] 233 | pub const mdbglvl_et_METIS_DBG_INFO: mdbglvl_et = 1; 234 | #[doc = "< Perform timing analysis"] 235 | pub const mdbglvl_et_METIS_DBG_TIME: mdbglvl_et = 2; 236 | #[doc = "< Show the coarsening progress"] 237 | pub const mdbglvl_et_METIS_DBG_COARSEN: mdbglvl_et = 4; 238 | #[doc = "< Show the refinement progress"] 239 | pub const mdbglvl_et_METIS_DBG_REFINE: mdbglvl_et = 8; 240 | #[doc = "< Show info on initial partitioning"] 241 | pub const mdbglvl_et_METIS_DBG_IPART: mdbglvl_et = 16; 242 | #[doc = "< Show info on vertex moves during refinement"] 243 | pub const mdbglvl_et_METIS_DBG_MOVEINFO: mdbglvl_et = 32; 244 | #[doc = "< Show info on vertex moves during sep refinement"] 245 | pub const mdbglvl_et_METIS_DBG_SEPINFO: mdbglvl_et = 64; 246 | #[doc = "< Show info on minimization of subdomain connectivity"] 247 | pub const mdbglvl_et_METIS_DBG_CONNINFO: mdbglvl_et = 128; 248 | #[doc = "< Show info on elimination of connected components"] 249 | pub const mdbglvl_et_METIS_DBG_CONTIGINFO: mdbglvl_et = 256; 250 | #[doc = "< Show info related to wspace allocation"] 251 | pub const mdbglvl_et_METIS_DBG_MEMORY: mdbglvl_et = 2048; 252 | #[doc = " Debug Levels"] 253 | pub type mdbglvl_et = ::std::os::raw::c_int; 254 | pub const mobjtype_et_METIS_OBJTYPE_CUT: mobjtype_et = 0; 255 | pub const mobjtype_et_METIS_OBJTYPE_VOL: mobjtype_et = 1; 256 | pub const mobjtype_et_METIS_OBJTYPE_NODE: mobjtype_et = 2; 257 | pub type mobjtype_et = ::std::os::raw::c_int; 258 | -------------------------------------------------------------------------------- /metis-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | #[cfg(not(feature = "vendored"))] 6 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 7 | 8 | #[cfg(feature = "vendored")] 9 | include!("../gen/bindings.rs"); 10 | -------------------------------------------------------------------------------- /metis-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a thin but idiomatic API around libmetis. 2 | //! 3 | //! See [`Graph`] for a usage example. 4 | 5 | #![deny(missing_docs)] 6 | 7 | use crate::option::Opt; 8 | use metis_sys as m; 9 | use std::convert::TryFrom; 10 | use std::fmt; 11 | use std::mem; 12 | use std::os; 13 | use std::ptr; 14 | use std::result::Result as StdResult; 15 | use std::slice; 16 | 17 | pub mod option; 18 | 19 | #[cfg(target_pointer_width = "16")] 20 | compile_error!("METIS does not support 16-bit architectures"); 21 | 22 | /// Integer type used by METIS, can either be an [`i32`] or an [`i64`]. 23 | pub type Idx = m::idx_t; 24 | 25 | /// Floating-point type used by METIS, can either be an [`f32`] or an [`f64`]. 26 | pub type Real = m::real_t; 27 | 28 | /// The length of the `options` array. 29 | /// 30 | /// See [`Graph::set_options`] for an example. It is also used in 31 | /// [`Mesh::set_options`]. 32 | pub const NOPTIONS: usize = m::METIS_NOPTIONS as usize; 33 | 34 | /// Error type returned by METIS. 35 | #[derive(Debug, PartialEq, Eq)] 36 | pub enum Error { 37 | /// Input is invalid. 38 | /// 39 | /// These bindings should check for most input errors, if not all. 40 | Input, 41 | 42 | /// METIS hit an out-of-memory error. 43 | Memory, 44 | 45 | /// METIS returned an error but its meaning is unknown. 46 | Other, 47 | } 48 | 49 | impl std::error::Error for Error {} 50 | 51 | impl From for Error { 52 | fn from(_: NewGraphError) -> Self { 53 | Self::Input 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(_: NewMeshError) -> Self { 59 | Self::Input 60 | } 61 | } 62 | 63 | impl fmt::Display for Error { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | match self { 66 | Error::Input => write!(f, "invalid input"), 67 | Error::Memory => write!(f, "out of memory"), 68 | Error::Other => write!(f, "METIS returned an error"), 69 | } 70 | } 71 | } 72 | 73 | /// The result of a partitioning. 74 | pub type Result = StdResult; 75 | 76 | trait ErrorCode { 77 | /// Makes a [`Result`] from a return code (int) from METIS. 78 | fn wrap(self) -> Result<()>; 79 | } 80 | 81 | impl ErrorCode for m::rstatus_et { 82 | fn wrap(self) -> Result<()> { 83 | match self { 84 | m::rstatus_et_METIS_OK => Ok(()), 85 | m::rstatus_et_METIS_ERROR_INPUT => Err(Error::Input), 86 | m::rstatus_et_METIS_ERROR_MEMORY => Err(Error::Memory), 87 | m::rstatus_et_METIS_ERROR => Err(Error::Other), 88 | other => panic!("unexpected error code ({}) from METIS", other), 89 | } 90 | } 91 | } 92 | 93 | /// Error raised when the graph data fed to [`Graph::new`] cannot be safely 94 | /// passed to METIS. 95 | /// 96 | /// Graph data must follow the format described in [`Graph::new`]. 97 | #[derive(Debug)] 98 | pub struct InvalidGraphError { 99 | msg: &'static str, 100 | } 101 | 102 | impl fmt::Display for InvalidGraphError { 103 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 104 | self.msg.fmt(f) 105 | } 106 | } 107 | 108 | /// Error type returned by [`Graph::new`]. 109 | /// 110 | /// Unlike [`Error`], this error originates from the Rust bindings. 111 | #[derive(Debug)] 112 | #[non_exhaustive] 113 | pub enum NewGraphError { 114 | /// `ncon` must be greater than 1. 115 | NoConstraints, 116 | 117 | /// `nparts` must be greater than 1. 118 | NoParts, 119 | 120 | /// Graph is too large. One of the array's length doesn't fit into [`Idx`]. 121 | TooLarge, 122 | 123 | /// The input arrays are malformed and cannot be safely passed to METIS. 124 | /// 125 | /// Note that these bindings do not check for all the invariants. Some might 126 | /// be raised during [`Graph::part_recursive`] and [`Graph::part_kway`] as 127 | /// [`Error::Input`]. 128 | InvalidGraph(InvalidGraphError), 129 | } 130 | 131 | impl fmt::Display for NewGraphError { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | match self { 134 | Self::NoConstraints => write!(f, "there must be at least one constraint"), 135 | Self::NoParts => write!(f, "there must be at least one part"), 136 | Self::TooLarge => write!(f, "graph is too large"), 137 | Self::InvalidGraph(err) => write!(f, "invalid graph structure: {err}"), 138 | } 139 | } 140 | } 141 | 142 | impl std::error::Error for NewGraphError {} 143 | 144 | impl NewGraphError { 145 | fn msg(msg: &'static str) -> Self { 146 | Self::InvalidGraph(InvalidGraphError { msg }) 147 | } 148 | } 149 | 150 | /// Helper function to convert an immutable slice ref to a mutable pointer 151 | unsafe fn slice_to_mut_ptr(slice: &[T]) -> *mut T { 152 | slice.as_ptr() as *mut T 153 | } 154 | 155 | /// Builder structure to set up a graph partition computation. 156 | /// 157 | /// This structure holds the required arguments for METIS to compute a 158 | /// partition. It also offers methods to easily set any optional argument. 159 | /// 160 | /// # Example 161 | /// 162 | /// ```rust 163 | /// # fn main() -> Result<(), metis::Error> { 164 | /// # use metis::Graph; 165 | /// // Make a graph with two vertices and an edge between the two. 166 | /// let xadj = &mut [0, 1, 2]; 167 | /// let adjncy = &mut [1, 0]; 168 | /// 169 | /// // Allocate the partition array which stores the partition of each vertex. 170 | /// let mut part = [0, 0]; 171 | /// 172 | /// // There are one constraint and two parts. The partitioning algorithm used 173 | /// // is recursive bisection. The k-way algorithm can also be used. 174 | /// Graph::new(1, 2, xadj, adjncy)? 175 | /// .part_recursive(&mut part)?; 176 | /// 177 | /// // The two vertices are placed in different parts. 178 | /// assert_ne!(part[0], part[1]); 179 | /// # Ok(()) 180 | /// # } 181 | /// ``` 182 | #[derive(Debug, PartialEq)] 183 | pub struct Graph<'a> { 184 | /// The number of balancing constrains. 185 | ncon: Idx, 186 | 187 | /// The number of parts to partition the graph. 188 | nparts: Idx, 189 | 190 | /// The adjency structure of the graph (part 1). 191 | xadj: &'a [Idx], 192 | 193 | /// The adjency structure of the graph (part 2). 194 | /// 195 | /// Required size: xadj.last() 196 | adjncy: &'a [Idx], 197 | 198 | /// The computational weights of the vertices. 199 | /// 200 | /// Required size: ncon * (xadj.len()-1) 201 | vwgt: Option<&'a [Idx]>, 202 | 203 | /// The communication weights of the vertices. 204 | /// 205 | /// Required size: xadj.len()-1 206 | vsize: Option<&'a [Idx]>, 207 | 208 | /// The weight of the edges. 209 | /// 210 | /// Required size: xadj.last() 211 | adjwgt: Option<&'a [Idx]>, 212 | 213 | /// The target partition weights of the vertices. 214 | /// 215 | /// If `None` then the graph is equally divided among the partitions. 216 | /// 217 | /// Required size: ncon * nparts 218 | tpwgts: Option<&'a [Real]>, 219 | 220 | /// Imbalance tolerances for each constraint. 221 | /// 222 | /// Required size: ncon 223 | ubvec: Option<&'a [Real]>, 224 | 225 | /// Fine-tuning parameters. 226 | options: [Idx; NOPTIONS], 227 | } 228 | 229 | impl<'a> Graph<'a> { 230 | /// Creates a new [`Graph`] object to be partitioned. 231 | /// 232 | /// - `ncon` is the number of constraints on each vertex (at least 1), 233 | /// - `nparts` is the number of parts wanted in the graph partition. 234 | /// 235 | /// # Input format 236 | /// 237 | /// CSR (Compressed Sparse Row) is a data structure for representing sparse 238 | /// matrices and is the primary data structure used by METIS. A CSR 239 | /// formatted graph is represented with two slices: an adjacency list 240 | /// (`adjcny`) and an index list (`xadj`). The nodes adjacent to node `n` 241 | /// are `adjncy[xadj[n]..xadj[n + 1]]`. Additionally, metis requires that 242 | /// graphs are undirected: if `(u, v)` is in the graph, then `(v, u)` must 243 | /// also be in the graph. 244 | /// 245 | /// Consider translating this simple graph to CSR format: 246 | /// ```rust 247 | /// // 5 - 3 - 4 - 0 248 | /// // | | / 249 | /// // 2 - 1 250 | /// let adjncy = [1, 4, 0, 2, 4, 1, 3, 2, 4, 5, 0, 1, 3, 3]; 251 | /// let xadj = [0, 2, 5, 7, 10, 13, 14]; 252 | /// 253 | /// // iterate over adjacent nodes 254 | /// let mut it = xadj 255 | /// .windows(2) 256 | /// .map(|x| &adjncy[x[0]..x[1]]); 257 | /// 258 | /// // node 0 is adjacent to nodes 1 and 4 259 | /// assert_eq!(it.next().unwrap(), &[1, 4]); 260 | /// 261 | /// // node 1 is adjacent to nodes 0, 2, and 4 262 | /// assert_eq!(it.next().unwrap(), &[0, 2, 4]); 263 | /// 264 | /// // node 2 is adjacent to nodes 1 and 3 265 | /// assert_eq!(it.next().unwrap(), &[1, 3]); 266 | /// 267 | /// // node 3 is adjacent to nodes 2, 4, and 5 268 | /// assert_eq!(it.next().unwrap(), &[2, 4, 5]); 269 | /// 270 | /// // node 4 is adjacent to nodes 0, 1, and 3 271 | /// assert_eq!(it.next().unwrap(), &[0, 1, 3]); 272 | /// 273 | /// // node 5 is adjacent to node 3 274 | /// assert_eq!(it.next().unwrap(), &[3]); 275 | /// 276 | /// assert!(it.next().is_none()); 277 | /// ``` 278 | /// 279 | /// More info can be found at: 280 | /// 281 | /// 282 | /// # Errors 283 | /// 284 | /// The following invariants must be held, otherwise this function returns 285 | /// an error: 286 | /// 287 | /// - all the arrays have a length that can be held by an [`Idx`], 288 | /// - `ncon` is strictly greater than zero, 289 | /// - `nparts` is strictly greater than zero, 290 | /// - `xadj` has at least one element (its length is the one more than the 291 | /// number of vertices), 292 | /// - `xadj` is sorted, 293 | /// - elements of `xadj` are positive, 294 | /// - the last element of `xadj` is the length of `adjncy`, 295 | /// - elements of `adjncy` are within zero and the number of vertices. 296 | /// 297 | /// # Mutability 298 | /// 299 | /// [`Graph::part_kway`] and [`Graph::part_recursive`] may mutate the 300 | /// contents of `xadj` and `adjncy`, but should revert all changes before 301 | /// returning. 302 | pub fn new( 303 | ncon: Idx, 304 | nparts: Idx, 305 | xadj: &'a [Idx], 306 | adjncy: &'a [Idx], 307 | ) -> StdResult, NewGraphError> { 308 | if ncon <= 0 { 309 | return Err(NewGraphError::NoConstraints); 310 | } 311 | if nparts <= 0 { 312 | return Err(NewGraphError::NoParts); 313 | } 314 | 315 | let last_xadj = *xadj 316 | .last() 317 | .ok_or(NewGraphError::msg("index list is empty"))?; 318 | let adjncy_len = Idx::try_from(adjncy.len()).map_err(|_| NewGraphError::TooLarge)?; 319 | if last_xadj != adjncy_len { 320 | return Err(NewGraphError::msg( 321 | "length mismatch between index and adjacency lists", 322 | )); 323 | } 324 | 325 | let nvtxs = match Idx::try_from(xadj.len()) { 326 | Ok(xadj_len) => xadj_len - 1, 327 | Err(_) => { 328 | return Err(NewGraphError::TooLarge); 329 | } 330 | }; 331 | 332 | let mut prev = 0; 333 | for x in xadj { 334 | if prev > *x { 335 | return Err(NewGraphError::msg("index list is not sorted")); 336 | } 337 | prev = *x; 338 | } 339 | 340 | for a in adjncy { 341 | if *a < 0 || *a >= nvtxs { 342 | return Err(NewGraphError::msg( 343 | "some values in the adjacency list are out of bounds", 344 | )); 345 | } 346 | } 347 | 348 | Ok(unsafe { Graph::new_unchecked(ncon, nparts, xadj, adjncy) }) 349 | } 350 | 351 | /// Creates a new [`Graph`] object to be partitioned (unchecked version). 352 | /// 353 | /// - `ncon` is the number of constraints on each vertex (at least 1), 354 | /// - `nparts` is the number of parts wanted in the graph partition. 355 | /// 356 | /// # Input format 357 | /// 358 | /// `xadj` and `adjncy` are the [CSR encoding][0] of the adjacency matrix 359 | /// that represents the graph. `xadj` is the row index and `adjncy` is the 360 | /// column index. 361 | /// 362 | /// [0]: https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format) 363 | /// 364 | /// # Safety 365 | /// 366 | /// This function still does some checks listed in "Panics" below. However, 367 | /// the caller is reponsible for upholding all invariants listed in the 368 | /// "Errors" section of [`Graph::new`]. Otherwise, the behavior of this 369 | /// function is undefined. 370 | /// 371 | /// # Panics 372 | /// 373 | /// This function panics if: 374 | /// - any of the arrays have a length that cannot be held by an [`Idx`], or 375 | /// - `ncon` is not strictly greater than zero, or 376 | /// - `nparts` is not strictly greater than zero, or 377 | /// - `xadj` is empty, or 378 | /// - the length of `adjncy` is different from the last element of `xadj`. 379 | /// 380 | /// # Mutability 381 | /// 382 | /// [`Graph::part_kway`] and [`Graph::part_recursive`] may mutate the 383 | /// contents of `xadj` and `adjncy`, but should revert all changes before 384 | /// returning. 385 | pub unsafe fn new_unchecked( 386 | ncon: Idx, 387 | nparts: Idx, 388 | xadj: &'a [Idx], 389 | adjncy: &'a [Idx], 390 | ) -> Graph<'a> { 391 | assert!(0 < ncon, "ncon must be strictly greater than zero"); 392 | assert!(0 < nparts, "nparts must be strictly greater than zero"); 393 | let _ = Idx::try_from(xadj.len()).expect("xadj array larger than Idx::MAX"); 394 | assert_ne!(xadj.len(), 0); 395 | let adjncy_len = Idx::try_from(adjncy.len()).expect("adjncy array larger than Idx::MAX"); 396 | assert_eq!(adjncy_len, *xadj.last().unwrap()); 397 | 398 | Graph { 399 | ncon, 400 | nparts, 401 | xadj, 402 | adjncy, 403 | vwgt: None, 404 | vsize: None, 405 | adjwgt: None, 406 | tpwgts: None, 407 | ubvec: None, 408 | options: [-1; NOPTIONS], 409 | } 410 | } 411 | 412 | /// Sets the computational weights of the vertices. 413 | /// 414 | /// By default, all vertices have the same weight. 415 | /// 416 | /// The `ncon` weights of the `i`th vertex must be located in 417 | /// `vwgt[i*ncon..(i+1)*ncon]`, and all elements of `vwgt` must be positive. 418 | /// 419 | /// # Panics 420 | /// 421 | /// This function panics if the length of `vwgt` is not `ncon` times the 422 | /// number of vertices. 423 | pub fn set_vwgt(mut self, vwgt: &'a [Idx]) -> Graph<'a> { 424 | let vwgt_len = Idx::try_from(vwgt.len()).expect("vwgt array too large"); 425 | assert_eq!(vwgt_len, self.ncon * (self.xadj.len() as Idx - 1)); 426 | self.vwgt = Some(vwgt); 427 | self 428 | } 429 | 430 | /// Sets the communication weights of the vertices. 431 | /// 432 | /// By default, all vertices have the same communication weight. 433 | /// 434 | /// Vertices can only have one communication weight. The length of `vsize` 435 | /// does not depend on `ncon`. 436 | /// 437 | /// # Panics 438 | /// 439 | /// This function panics if the length of `vsize` is not the number of 440 | /// vertices. 441 | pub fn set_vsize(mut self, vsize: &'a [Idx]) -> Graph<'a> { 442 | let vsize_len = Idx::try_from(vsize.len()).expect("vsize array too large"); 443 | assert_eq!(vsize_len, self.xadj.len() as Idx - 1); 444 | self.vsize = Some(vsize); 445 | self 446 | } 447 | 448 | /// Sets the weights of the edges. 449 | /// 450 | /// By default, all edges have the same weight. 451 | /// 452 | /// All elements of `adjwgt` must be positive. 453 | /// 454 | /// # Panics 455 | /// 456 | /// This function panics if the length of `adjwgt` is not equal to the 457 | /// length of `adjncy`. 458 | pub fn set_adjwgt(mut self, adjwgt: &'a [Idx]) -> Graph<'a> { 459 | let adjwgt_len = Idx::try_from(adjwgt.len()).expect("adjwgt array too large"); 460 | assert_eq!(adjwgt_len, *self.xadj.last().unwrap()); 461 | self.adjwgt = Some(adjwgt); 462 | self 463 | } 464 | 465 | /// Sets the target partition weights for each part and constraint. 466 | /// 467 | /// By default, the graph is divided equally. 468 | /// 469 | /// The target partition weight for the `i`th part and `j`th constraint is 470 | /// specified at `tpwgts[i*ncon+j]`. For each constraint `j`, the sum of the 471 | /// target partition weights must be 1.0. Meaning 472 | /// `(0..nparts).map(|i| tpwgts[i*ncon+j]).sum() == 1.0`. 473 | /// 474 | /// # Panics 475 | /// 476 | /// This function panics if the length of `tpwgts` is not equal to `ncon` 477 | /// times `nparts`. 478 | pub fn set_tpwgts(mut self, tpwgts: &'a [Real]) -> Graph<'a> { 479 | let tpwgts_len = Idx::try_from(tpwgts.len()).expect("tpwgts array too large"); 480 | assert_eq!(tpwgts_len, self.ncon * self.nparts); 481 | self.tpwgts = Some(tpwgts); 482 | self 483 | } 484 | 485 | /// Sets the load imbalance tolerance for each constraint. 486 | /// 487 | /// By default, it equals to 1.001 if `ncon` equals 1 and 1.01 otherwise. 488 | /// 489 | /// For the `i`th partition and `j`th constraint the allowed weight is the 490 | /// `ubvec[j]*tpwgts[i*ncon+j]` fraction of the `j`th's constraint total 491 | /// weight. The load imbalances must be greater than 1.0. 492 | /// 493 | /// # Panics 494 | /// 495 | /// This function panics if the length of `ubvec` is not equal to `ncon`. 496 | pub fn set_ubvec(mut self, ubvec: &'a [Real]) -> Graph<'a> { 497 | let ubvec_len = Idx::try_from(ubvec.len()).expect("ubvec array too large"); 498 | assert_eq!(ubvec_len, self.ncon); 499 | self.ubvec = Some(ubvec); 500 | self 501 | } 502 | 503 | /// Sets the fine-tuning parameters for this partitioning. 504 | /// 505 | /// When few options are to be set, [`Graph::set_option`] might be a 506 | /// better fit. 507 | /// 508 | /// See the [option] module for the list of available parameters. Note that 509 | /// not all are applicable to a given partitioning method. Refer to the 510 | /// documentation of METIS ([link]) for more info on this. 511 | /// 512 | /// [link]: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf 513 | /// 514 | /// # Example 515 | /// 516 | /// ```rust 517 | /// # fn main() -> Result<(), metis::Error> { 518 | /// # use metis::Graph; 519 | /// use metis::option::Opt as _; 520 | /// 521 | /// let xadj = &[0, 1, 2]; 522 | /// let adjncy = &[1, 0]; 523 | /// let mut part = [0, 0]; 524 | /// 525 | /// // -1 is the default value. 526 | /// let mut options = [-1; metis::NOPTIONS]; 527 | /// 528 | /// // four refinement iterations instead of the default 10. 529 | /// options[metis::option::NIter::INDEX] = 4; 530 | /// 531 | /// Graph::new(1, 2, xadj, adjncy)? 532 | /// .set_options(&options) 533 | /// .part_recursive(&mut part)?; 534 | /// 535 | /// // The two vertices are placed in different parts. 536 | /// assert_ne!(part[0], part[1]); 537 | /// # Ok(()) 538 | /// # } 539 | /// ``` 540 | pub fn set_options(mut self, options: &[Idx; NOPTIONS]) -> Graph<'a> { 541 | self.options.copy_from_slice(options); 542 | self 543 | } 544 | 545 | /// Sets a fine-tuning parameter for this partitioning. 546 | /// 547 | /// When options are to be set in batches, [`Graph::set_options`] might be a 548 | /// better fit. 549 | /// 550 | /// See the [option] module for the list of available parameters. Note that 551 | /// not all are applicable to a given partitioning method. Refer to the 552 | /// documentation of METIS ([link]) for more info on this. 553 | /// 554 | /// [link]: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf 555 | /// 556 | /// # Example 557 | /// 558 | /// ```rust 559 | /// # fn main() -> Result<(), metis::Error> { 560 | /// # use metis::Graph; 561 | /// let xadj = &[0, 1, 2]; 562 | /// let adjncy = &[1, 0]; 563 | /// let mut part = [0, 0]; 564 | /// 565 | /// Graph::new(1, 2, xadj, adjncy)? 566 | /// .set_option(metis::option::NIter(4)) 567 | /// .part_recursive(&mut part)?; 568 | /// 569 | /// // The two vertices are placed in different parts. 570 | /// assert_ne!(part[0], part[1]); 571 | /// # Ok(()) 572 | /// # } 573 | /// ``` 574 | pub fn set_option(mut self, option: O) -> Graph<'a> 575 | where 576 | O: option::Opt, 577 | { 578 | self.options[O::INDEX] = option.value(); 579 | self 580 | } 581 | 582 | /// Partition the graph using multilevel recursive bisection. 583 | /// 584 | /// Returns the edge-cut, the total communication volume of the 585 | /// partitioning solution. 586 | /// 587 | /// Equivalent of `METIS_PartGraphRecursive`. 588 | /// 589 | /// # Panics 590 | /// 591 | /// This function panics if the length of `part` is not the number of 592 | /// vertices. 593 | pub fn part_recursive(mut self, part: &mut [Idx]) -> Result { 594 | self.options[option::Numbering::INDEX] = option::Numbering::C.value(); 595 | let part_len = Idx::try_from(part.len()).expect("part array larger than Idx::MAX"); 596 | assert_eq!( 597 | part_len, 598 | self.xadj.len() as Idx - 1, 599 | "part.len() must be equal to the number of vertices", 600 | ); 601 | 602 | if self.nparts == 1 { 603 | // METIS does not handle this case well. 604 | part.fill(0); 605 | return Ok(0); 606 | } 607 | 608 | let nvtxs = self.xadj.len() as Idx - 1; 609 | let mut edgecut = mem::MaybeUninit::uninit(); 610 | let part = part.as_mut_ptr(); 611 | unsafe { 612 | m::METIS_PartGraphRecursive( 613 | &nvtxs as *const Idx as *mut Idx, 614 | &self.ncon as *const Idx as *mut Idx, 615 | slice_to_mut_ptr(self.xadj), 616 | slice_to_mut_ptr(self.adjncy), 617 | self.vwgt 618 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 619 | self.vsize 620 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 621 | self.adjwgt 622 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 623 | &self.nparts as *const Idx as *mut Idx, 624 | self.tpwgts 625 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 626 | self.ubvec 627 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 628 | slice_to_mut_ptr(&self.options), 629 | edgecut.as_mut_ptr(), 630 | part, 631 | ) 632 | .wrap()?; 633 | Ok(edgecut.assume_init()) 634 | } 635 | } 636 | 637 | /// Partition the graph using multilevel k-way partitioning. 638 | /// 639 | /// Returns the edge-cut, the total communication volume of the 640 | /// partitioning solution. 641 | /// 642 | /// Equivalent of `METIS_PartGraphKway`. 643 | /// 644 | /// # Panics 645 | /// 646 | /// This function panics if the length of `part` is not the number of 647 | /// vertices. 648 | pub fn part_kway(self, part: &mut [Idx]) -> Result { 649 | let part_len = Idx::try_from(part.len()).expect("part array larger than Idx::MAX"); 650 | assert_eq!( 651 | part_len, 652 | self.xadj.len() as Idx - 1, 653 | "part.len() must be equal to the number of vertices", 654 | ); 655 | 656 | if self.nparts == 1 { 657 | // METIS does not handle this case well. 658 | part.fill(0); 659 | return Ok(0); 660 | } 661 | 662 | let nvtxs = self.xadj.len() as Idx - 1; 663 | let mut edgecut = mem::MaybeUninit::uninit(); 664 | let part = part.as_mut_ptr(); 665 | unsafe { 666 | m::METIS_PartGraphKway( 667 | &nvtxs as *const Idx as *mut Idx, 668 | &self.ncon as *const Idx as *mut Idx, 669 | slice_to_mut_ptr(self.xadj), 670 | slice_to_mut_ptr(self.adjncy), 671 | self.vwgt 672 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 673 | self.vsize 674 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 675 | self.adjwgt 676 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 677 | &self.nparts as *const Idx as *mut Idx, 678 | self.tpwgts 679 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 680 | self.ubvec 681 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 682 | slice_to_mut_ptr(&self.options), 683 | edgecut.as_mut_ptr(), 684 | part, 685 | ) 686 | .wrap()?; 687 | Ok(edgecut.assume_init()) 688 | } 689 | } 690 | } 691 | 692 | /// Error raised when the mesh data fed to [`Mesh::new`] cannot be safely passed 693 | /// to METIS. 694 | /// 695 | /// Mesh data must follow the format described in [`Mesh::new`]. 696 | #[derive(Debug)] 697 | pub struct InvalidMeshError { 698 | msg: &'static str, 699 | } 700 | 701 | impl fmt::Display for InvalidMeshError { 702 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 703 | self.msg.fmt(f) 704 | } 705 | } 706 | 707 | /// Error type returned by [`Mesh::new`]. 708 | /// 709 | /// Unlike [`Error`], this error originates from the Rust bindings. 710 | #[derive(Debug)] 711 | #[non_exhaustive] 712 | pub enum NewMeshError { 713 | /// `nparts` must be greater than 1. 714 | NoParts, 715 | 716 | /// Mesh is too large. One of the array's length doesn't fit into [`Idx`]. 717 | TooLarge, 718 | 719 | /// The input arrays are malformed and cannot be safely passed to METIS. 720 | /// 721 | /// Note that these bindings do not check for all the invariants. Some might 722 | /// be raised during [`Mesh::part_dual`] and [`Mesh::part_nodal`] as 723 | /// [`Error::Input`]. 724 | InvalidMesh(InvalidMeshError), 725 | } 726 | 727 | impl fmt::Display for NewMeshError { 728 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 729 | match self { 730 | Self::NoParts => write!(f, "there must be at least one part"), 731 | Self::TooLarge => write!(f, "mesh is too large"), 732 | Self::InvalidMesh(err) => write!(f, "invalid mesh structure: {err}"), 733 | } 734 | } 735 | } 736 | 737 | impl std::error::Error for NewMeshError {} 738 | 739 | impl NewMeshError { 740 | fn msg(msg: &'static str) -> Self { 741 | Self::InvalidMesh(InvalidMeshError { msg }) 742 | } 743 | } 744 | 745 | /// Returns the number of elements and the number of nodes in the mesh. 746 | fn check_mesh_structure(eptr: &[Idx], eind: &[Idx]) -> StdResult<(Idx, Idx), NewMeshError> { 747 | let last_eptr = *eptr 748 | .last() 749 | .ok_or(NewMeshError::msg("element index is empty"))?; 750 | let eind_len = Idx::try_from(eind.len()).map_err(|_| NewMeshError::TooLarge)?; 751 | if last_eptr != eind_len { 752 | return Err(NewMeshError::msg( 753 | "length mismatch between element and node indices", 754 | )); 755 | } 756 | 757 | let ne = Idx::try_from(eptr.len()).map_err(|_| NewMeshError::TooLarge)? - 1; 758 | 759 | let mut prev = 0; 760 | for x in eptr { 761 | if prev > *x { 762 | return Err(NewMeshError::msg("element index is not sorted")); 763 | } 764 | prev = *x; 765 | } 766 | 767 | let mut max_node = 0; 768 | for a in eind { 769 | if *a < 0 { 770 | return Err(NewMeshError::msg( 771 | "values in the node index are out of bounds", 772 | )); 773 | } 774 | if *a > max_node { 775 | max_node = *a; 776 | } 777 | } 778 | 779 | Ok((ne, max_node + 1)) 780 | } 781 | 782 | /// Builder structure to set up a mesh partition computation. 783 | /// 784 | /// This structure holds the required arguments for METIS to compute a 785 | /// partition. It also offers methods to easily set any optional argument. 786 | /// 787 | /// # Example 788 | /// 789 | /// Usage is fairly similar to [`Graph`]. Refer to its documentation for 790 | /// details. 791 | #[derive(Debug, PartialEq)] 792 | pub struct Mesh<'a> { 793 | /// The number of nodes in the mesh. 794 | nn: Idx, 795 | 796 | /// The number of parts to partition the mesh. 797 | nparts: Idx, 798 | 799 | /// The number of nodes two elements must share for an edge to appear in the 800 | /// dual graph. 801 | ncommon: Idx, 802 | 803 | eptr: &'a [Idx], // mesh representation 804 | eind: &'a [Idx], // mesh repr 805 | 806 | /// The computational weights of the elements. 807 | /// 808 | /// Required size: ne 809 | vwgt: Option<&'a [Idx]>, 810 | 811 | /// The communication weights of the elements. 812 | /// 813 | /// Required size: ne 814 | vsize: Option<&'a [Idx]>, 815 | 816 | /// The target partition weights of the elements. 817 | /// 818 | /// If `None` then the mesh is equally divided among the partitions. 819 | /// 820 | /// Required size: nparts 821 | tpwgts: Option<&'a [Real]>, 822 | 823 | /// Fine-tuning parameters. 824 | options: [Idx; NOPTIONS], 825 | } 826 | 827 | impl<'a> Mesh<'a> { 828 | /// Creates a new [`Mesh`] object to be partitioned. 829 | /// 830 | /// `nparts` is the number of parts wanted in the mesh partition. 831 | /// 832 | /// # Input format 833 | /// 834 | /// The length of `eptr` is `n + 1`, where `n` is the number of elements in 835 | /// the mesh. The length of `eind` is the sum of the number of nodes in all 836 | /// the elements of the mesh. The list of nodes belonging to the `i`th 837 | /// element of the mesh are stored in consecutive locations of `eind` 838 | /// starting at position `eptr[i]` up to (but not including) position 839 | /// `eptr[i+1]`. 840 | /// 841 | /// # Errors 842 | /// 843 | /// The following invariants must be held, otherwise this function returns 844 | /// an error: 845 | /// 846 | /// - `nparts` is strictly greater than zero, 847 | /// - `eptr` has at least one element (its length is the one more than the 848 | /// number of mesh elements), 849 | /// - `eptr` is sorted, 850 | /// - elements of `eptr` are positive, 851 | /// - the last element of `eptr` is the length of `eind`, 852 | /// - all the arrays have a length that can be held by an [`Idx`]. 853 | /// 854 | /// # Mutability 855 | /// 856 | /// [`Mesh::part_dual`] and [`Mesh::part_nodal`] may mutate the contents of 857 | /// `eptr` and `eind`, but should revert all changes before returning. 858 | pub fn new(nparts: Idx, eptr: &'a [Idx], eind: &'a [Idx]) -> StdResult, NewMeshError> { 859 | if nparts <= 0 { 860 | return Err(NewMeshError::NoParts); 861 | } 862 | let (_ne, nn) = check_mesh_structure(eptr, eind)?; 863 | Ok(unsafe { Mesh::new_unchecked(nn, nparts, eptr, eind) }) 864 | } 865 | 866 | /// Creates a new [`Mesh`] object to be partitioned (unchecked version). 867 | /// 868 | /// - `nn` is the number of nodes in the mesh, 869 | /// - `nparts` is the number of parts wanted in the mesh partition. 870 | /// 871 | /// # Input format 872 | /// 873 | /// See [`Mesh::new`]. 874 | /// 875 | /// # Safety 876 | /// 877 | /// This function still does some checks listed in "Panics" below. However, 878 | /// the caller is reponsible for upholding all invariants listed in the 879 | /// "Errors" section of [`Mesh::new`]. Otherwise, the behavior of this 880 | /// function is undefined. 881 | /// 882 | /// # Panics 883 | /// 884 | /// This function panics if: 885 | /// - any of the arrays have a length that cannot be hold by an [`Idx`], or 886 | /// - `nn` is not strictly greater than zero, or 887 | /// - `nparts` is not strictly greater than zero, or 888 | /// - `eptr` is empty, or 889 | /// - the length of `eind` is different from the last element of `eptr`. 890 | /// 891 | /// # Mutability 892 | /// 893 | /// While nothing should be modified by the [`Mesh`] structure, METIS 894 | /// doesn't specify any `const` modifier, so everything must be mutable on 895 | /// Rust's side. 896 | pub unsafe fn new_unchecked( 897 | nn: Idx, 898 | nparts: Idx, 899 | eptr: &'a [Idx], 900 | eind: &'a [Idx], 901 | ) -> Mesh<'a> { 902 | assert!(0 < nn, "nn must be strictly greater than zero"); 903 | assert!(0 < nparts, "nparts must be strictly greater than zero"); 904 | let _ = Idx::try_from(eptr.len()).expect("eptr array larger than Idx::MAX"); 905 | assert_ne!(eptr.len(), 0); 906 | let eind_len = Idx::try_from(eind.len()).expect("eind array larger than Idx::MAX"); 907 | assert_eq!(eind_len, *eptr.last().unwrap()); 908 | 909 | Mesh { 910 | nn, 911 | nparts, 912 | ncommon: 1, 913 | eptr, 914 | eind, 915 | vwgt: None, 916 | vsize: None, 917 | tpwgts: None, 918 | options: [-1; NOPTIONS], 919 | } 920 | } 921 | 922 | /// Sets the computational weights of the elements. 923 | /// 924 | /// By default, all elements have the same weight. 925 | /// 926 | /// All elements of `vwgt` must be positive. 927 | /// 928 | /// # Panics 929 | /// 930 | /// This function panics if the length of `vwgt` is not the number of 931 | /// elements. 932 | pub fn set_vwgt(mut self, vwgt: &'a [Idx]) -> Mesh<'a> { 933 | let vwgt_len = Idx::try_from(vwgt.len()).expect("vwgt array too large"); 934 | assert_eq!(vwgt_len, self.eptr.len() as Idx - 1); 935 | self.vwgt = Some(vwgt); 936 | self 937 | } 938 | 939 | /// Sets the communication weights of the elements. 940 | /// 941 | /// By default, all elements have the same communication weight. 942 | /// 943 | /// # Panics 944 | /// 945 | /// This function panics if the length of `vsize` is not the number of 946 | /// elements. 947 | pub fn set_vsize(mut self, vsize: &'a [Idx]) -> Mesh<'a> { 948 | let vsize_len = Idx::try_from(vsize.len()).expect("vsize array too large"); 949 | assert_eq!(vsize_len, self.eptr.len() as Idx - 1); 950 | self.vsize = Some(vsize); 951 | self 952 | } 953 | 954 | /// Sets the target partition weights for each part. 955 | /// 956 | /// By default, the mesh is divided equally. 957 | /// 958 | /// The sum of the target partition weights must be 1.0. 959 | /// 960 | /// # Panics 961 | /// 962 | /// This function panics if the length of `tpwgts` is not equal to `nparts`. 963 | pub fn set_tpwgts(mut self, tpwgts: &'a [Real]) -> Mesh<'a> { 964 | let tpwgts_len = Idx::try_from(tpwgts.len()).expect("tpwgts array too large"); 965 | assert_eq!(tpwgts_len, self.nparts); 966 | self.tpwgts = Some(tpwgts); 967 | self 968 | } 969 | 970 | /// Sets the fine-tuning parameters for this partitioning. 971 | /// 972 | /// When few options are to be set, [`Mesh::set_option`] might be a 973 | /// better fit. 974 | /// 975 | /// See the [option] module for the list of available parameters. Note that 976 | /// not all are applicable to a given partitioning method. Refer to the 977 | /// documentation of METIS ([link]) for more info on this. 978 | /// 979 | /// See [`Graph::set_options`] for a usage example. 980 | /// 981 | /// [link]: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf 982 | pub fn set_options(mut self, options: &[Idx; NOPTIONS]) -> Mesh<'a> { 983 | self.options.copy_from_slice(options); 984 | self 985 | } 986 | 987 | /// Sets a fine-tuning parameter for this partitioning. 988 | /// 989 | /// When options are to be set in batches, [`Mesh::set_options`] might be a 990 | /// better fit. 991 | /// 992 | /// See the [option] module for the list of available parameters. Note that 993 | /// not all are applicable to a given partitioning method. Refer to the 994 | /// documentation of METIS ([link]) for more info on this. 995 | /// 996 | /// See [`Graph::set_option`] for a usage example. 997 | /// 998 | /// [link]: http://glaros.dtc.umn.edu/gkhome/fetch/sw/metis/manual.pdf 999 | pub fn set_option(mut self, option: O) -> Mesh<'a> 1000 | where 1001 | O: option::Opt, 1002 | { 1003 | self.options[O::INDEX] = option.value(); 1004 | self 1005 | } 1006 | 1007 | /// Partition the mesh using its dual graph. 1008 | /// 1009 | /// Returns the edge-cut, the total communication volume of the 1010 | /// partitioning solution. 1011 | /// 1012 | /// Equivalent of `METIS_PartMeshDual`. 1013 | /// 1014 | /// # Panics 1015 | /// 1016 | /// This function panics if the length of `epart` is not the number of 1017 | /// elements, or if `nparts`'s is not the number of nodes. 1018 | pub fn part_dual(mut self, epart: &mut [Idx], npart: &mut [Idx]) -> Result { 1019 | self.options[option::Numbering::INDEX] = option::Numbering::C.value(); 1020 | let epart_len = Idx::try_from(epart.len()).expect("epart array larger than Idx::MAX"); 1021 | assert_eq!( 1022 | epart_len, 1023 | self.eptr.len() as Idx - 1, 1024 | "epart.len() must be equal to the number of elements", 1025 | ); 1026 | let npart_len = Idx::try_from(npart.len()).expect("npart array larger than Idx::MAX"); 1027 | assert_eq!( 1028 | npart_len, self.nn, 1029 | "npart.len() must be equal to the number of nodes", 1030 | ); 1031 | 1032 | if self.nparts == 1 { 1033 | // METIS does not handle this case well. 1034 | epart.fill(0); 1035 | npart.fill(0); 1036 | return Ok(0); 1037 | } 1038 | 1039 | let ne = self.eptr.len() as Idx - 1; 1040 | let mut edgecut = mem::MaybeUninit::uninit(); 1041 | unsafe { 1042 | m::METIS_PartMeshDual( 1043 | &ne as *const Idx as *mut Idx, 1044 | &self.nn as *const Idx as *mut Idx, 1045 | slice_to_mut_ptr(self.eptr), 1046 | slice_to_mut_ptr(self.eind), 1047 | self.vwgt 1048 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1049 | self.vsize 1050 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1051 | &self.ncommon as *const Idx as *mut Idx, 1052 | &self.nparts as *const Idx as *mut Idx, 1053 | self.tpwgts 1054 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1055 | slice_to_mut_ptr(&self.options), 1056 | edgecut.as_mut_ptr(), 1057 | epart.as_mut_ptr(), 1058 | npart.as_mut_ptr(), 1059 | ) 1060 | .wrap()?; 1061 | Ok(edgecut.assume_init()) 1062 | } 1063 | } 1064 | 1065 | /// Partition the mesh using its nodal graph. 1066 | /// 1067 | /// Returns the edge-cut, the total communication volume of the 1068 | /// partitioning solution. 1069 | /// 1070 | /// Previous settings of `ncommon` are not used by this function. 1071 | /// 1072 | /// Equivalent of `METIS_PartMeshNodal`. 1073 | /// 1074 | /// # Panics 1075 | /// 1076 | /// This function panics if the length of `epart` is not the number of 1077 | /// elements, or if `nparts`'s is not the number of nodes. 1078 | pub fn part_nodal(mut self, epart: &mut [Idx], npart: &mut [Idx]) -> Result { 1079 | self.options[option::Numbering::INDEX] = option::Numbering::C.value(); 1080 | let epart_len = Idx::try_from(epart.len()).expect("epart array larger than Idx::MAX"); 1081 | assert_eq!( 1082 | epart_len, 1083 | self.eptr.len() as Idx - 1, 1084 | "epart.len() must be equal to the number of elements", 1085 | ); 1086 | let npart_len = Idx::try_from(npart.len()).expect("npart array larger than Idx::MAX"); 1087 | assert_eq!( 1088 | npart_len, self.nn, 1089 | "npart.len() must be equal to the number of nodes", 1090 | ); 1091 | 1092 | if self.nparts == 1 { 1093 | // METIS does not handle this case well. 1094 | epart.fill(0); 1095 | npart.fill(0); 1096 | return Ok(0); 1097 | } 1098 | 1099 | let ne = self.eptr.len() as Idx - 1; 1100 | let mut edgecut = mem::MaybeUninit::uninit(); 1101 | unsafe { 1102 | m::METIS_PartMeshNodal( 1103 | &ne as *const Idx as *mut Idx, 1104 | &self.nn as *const Idx as *mut Idx, 1105 | slice_to_mut_ptr(self.eptr), 1106 | slice_to_mut_ptr(self.eind), 1107 | self.vwgt 1108 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1109 | self.vsize 1110 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1111 | &self.nparts as *const Idx as *mut Idx, 1112 | self.tpwgts 1113 | .map_or_else(ptr::null_mut, |s| slice_to_mut_ptr(s)), 1114 | slice_to_mut_ptr(&self.options), 1115 | edgecut.as_mut_ptr(), 1116 | epart.as_mut_ptr(), 1117 | npart.as_mut_ptr(), 1118 | ) 1119 | .wrap()?; 1120 | Ok(edgecut.assume_init()) 1121 | } 1122 | } 1123 | } 1124 | 1125 | /// The dual of a mesh. 1126 | /// 1127 | /// Result of [`mesh_to_dual`]. 1128 | #[derive(Debug, PartialEq, Eq)] 1129 | pub struct Dual { 1130 | xadj: &'static mut [Idx], 1131 | adjncy: &'static mut [Idx], 1132 | } 1133 | 1134 | impl Dual { 1135 | /// The adjacency index array. 1136 | pub fn xadj(&self) -> &[Idx] { 1137 | self.xadj 1138 | } 1139 | 1140 | /// The adjacency array. 1141 | pub fn adjncy(&self) -> &[Idx] { 1142 | self.adjncy 1143 | } 1144 | 1145 | /// The adjacency index array, and the adjacency array as mutable slices. 1146 | pub fn as_mut(&mut self) -> (&mut [Idx], &mut [Idx]) { 1147 | (self.xadj, self.adjncy) 1148 | } 1149 | } 1150 | 1151 | impl Drop for Dual { 1152 | fn drop(&mut self) { 1153 | unsafe { 1154 | m::METIS_Free(self.xadj.as_mut_ptr() as *mut os::raw::c_void); 1155 | m::METIS_Free(self.adjncy.as_mut_ptr() as *mut os::raw::c_void); 1156 | } 1157 | } 1158 | } 1159 | 1160 | /// Generate the dual graph of a mesh. 1161 | /// 1162 | /// # Errors 1163 | /// 1164 | /// This function returns an error if `eptr` and `eind` don't follow the mesh 1165 | /// format given in [`Mesh::new`]. 1166 | pub fn mesh_to_dual(eptr: &[Idx], eind: &[Idx], ncommon: Idx) -> Result { 1167 | let (ne, nn) = check_mesh_structure(eptr, eind)?; 1168 | let mut xadj = mem::MaybeUninit::uninit(); 1169 | let mut adjncy = mem::MaybeUninit::uninit(); 1170 | let numbering_flag = 0; 1171 | 1172 | // SAFETY: METIS_MeshToDual allocates the xadj and adjncy arrays. 1173 | // SAFETY: hopefully those arrays are of correct length. 1174 | unsafe { 1175 | m::METIS_MeshToDual( 1176 | &ne as *const Idx as *mut Idx, 1177 | &nn as *const Idx as *mut Idx, 1178 | slice_to_mut_ptr(eptr), 1179 | slice_to_mut_ptr(eind), 1180 | &ncommon as *const Idx as *mut Idx, 1181 | &numbering_flag as *const Idx as *mut Idx, 1182 | xadj.as_mut_ptr(), 1183 | adjncy.as_mut_ptr(), 1184 | ) 1185 | .wrap()?; 1186 | let xadj = xadj.assume_init(); 1187 | let xadj = slice::from_raw_parts_mut(xadj, eptr.len()); 1188 | let adjncy = adjncy.assume_init(); 1189 | let adjncy = slice::from_raw_parts_mut(adjncy, xadj[xadj.len() - 1] as usize); 1190 | Ok(Dual { xadj, adjncy }) 1191 | } 1192 | } 1193 | -------------------------------------------------------------------------------- /src/option.rs: -------------------------------------------------------------------------------- 1 | //! Fine-tuning parameter types. 2 | //! 3 | //! For options that take an integer value, should this value be negative, the 4 | //! default will be used, if any. 5 | 6 | // Idx and Real can be 32 or 64 bits. Make sure to suppress warnings when 7 | // casts turn out to be trivial. 8 | #![allow(trivial_numeric_casts)] 9 | 10 | use crate::m; 11 | use crate::Idx; 12 | 13 | mod private { 14 | pub trait Sealed {} 15 | } 16 | 17 | /// Trait implemented by METIS' options. 18 | /// 19 | /// See [`crate::Graph::set_options`] for an example. It is also used in 20 | /// [`crate::Mesh::set_options`]. 21 | pub trait Opt: private::Sealed { 22 | /// Index of the option in the array from [`crate::Graph::set_options`] and 23 | /// [`crate::Mesh::set_options`]. 24 | const INDEX: usize; 25 | 26 | /// Convert the value into metis' format, for use with 27 | /// [`crate::Graph::set_options`] and [`crate::Mesh::set_options`]. 28 | fn value(self) -> Idx; 29 | } 30 | 31 | /// Specifies the partitioning method. 32 | pub enum PType { 33 | /// Multilevel recursive bisection. 34 | Rb, 35 | 36 | /// Multilevel k-way partitioning. 37 | Kway, 38 | } 39 | 40 | impl private::Sealed for PType {} 41 | impl Opt for PType { 42 | const INDEX: usize = m::moptions_et_METIS_OPTION_PTYPE as usize; 43 | 44 | fn value(self) -> Idx { 45 | match self { 46 | PType::Rb => m::mptype_et_METIS_PTYPE_RB as Idx, 47 | PType::Kway => m::mptype_et_METIS_PTYPE_KWAY as Idx, 48 | } 49 | } 50 | } 51 | 52 | /// Specifies the type of objective. 53 | pub enum ObjType { 54 | /// Edge-cut minimization. 55 | Cut, 56 | 57 | /// Total communication volume minimization. 58 | Vol, 59 | } 60 | 61 | impl private::Sealed for ObjType {} 62 | impl Opt for ObjType { 63 | const INDEX: usize = m::moptions_et_METIS_OPTION_OBJTYPE as usize; 64 | 65 | fn value(self) -> Idx { 66 | match self { 67 | ObjType::Cut => m::mobjtype_et_METIS_OBJTYPE_CUT as Idx, 68 | ObjType::Vol => m::mobjtype_et_METIS_OBJTYPE_VOL as Idx, 69 | } 70 | } 71 | } 72 | 73 | /// Specifies the matching scheme to be used during coarsening. 74 | pub enum CType { 75 | /// Random matching. 76 | Rm, 77 | 78 | /// Sorted heavy-edge matching. 79 | Shem, 80 | } 81 | 82 | impl private::Sealed for CType {} 83 | impl Opt for CType { 84 | const INDEX: usize = m::moptions_et_METIS_OPTION_CTYPE as usize; 85 | 86 | fn value(self) -> Idx { 87 | match self { 88 | CType::Rm => m::mctype_et_METIS_CTYPE_RM as Idx, 89 | CType::Shem => m::mctype_et_METIS_CTYPE_SHEM as Idx, 90 | } 91 | } 92 | } 93 | 94 | /// Determines the algorithm used during initial partitioning. 95 | pub enum IpType { 96 | /// Grows a bisection using a greedy strategy. 97 | Grow, 98 | 99 | /// Compute a bisection at random followed by a refinement. 100 | Random, 101 | 102 | /// Derives a separator from an edge cut. 103 | Edge, 104 | 105 | /// Grow a bisection using a greedy node-based strategy. 106 | Node, 107 | } 108 | 109 | impl private::Sealed for IpType {} 110 | impl Opt for IpType { 111 | const INDEX: usize = m::moptions_et_METIS_OPTION_IPTYPE as usize; 112 | 113 | fn value(self) -> Idx { 114 | match self { 115 | IpType::Grow => m::miptype_et_METIS_IPTYPE_GROW as Idx, 116 | IpType::Random => m::miptype_et_METIS_IPTYPE_RANDOM as Idx, 117 | IpType::Edge => m::miptype_et_METIS_IPTYPE_EDGE as Idx, 118 | IpType::Node => m::miptype_et_METIS_IPTYPE_NODE as Idx, 119 | } 120 | } 121 | } 122 | 123 | /// Determines the algorithm used for refinement. 124 | pub enum RType { 125 | /// FM-based cut refinement. 126 | Fm, 127 | 128 | /// Greedy-based cut and volume refinement. 129 | Greedy, 130 | 131 | /// Two-sided FM refinement. 132 | Sep2Sided, 133 | 134 | /// One-sided FM refinement. 135 | Sep1Sided, 136 | } 137 | 138 | impl private::Sealed for RType {} 139 | impl Opt for RType { 140 | const INDEX: usize = m::moptions_et_METIS_OPTION_RTYPE as usize; 141 | 142 | fn value(self) -> Idx { 143 | match self { 144 | RType::Fm => m::mrtype_et_METIS_RTYPE_FM as Idx, 145 | RType::Greedy => m::mrtype_et_METIS_RTYPE_GREEDY as Idx, 146 | RType::Sep2Sided => m::mrtype_et_METIS_RTYPE_SEP2SIDED as Idx, 147 | RType::Sep1Sided => m::mrtype_et_METIS_RTYPE_SEP1SIDED as Idx, 148 | } 149 | } 150 | } 151 | 152 | /// Specifies the number of different partitions that it will compute. The 153 | /// final partition is the one that achieves the best edge cut or 154 | /// communication volume. Default is 1. 155 | pub struct NCuts(pub Idx); 156 | 157 | impl private::Sealed for NCuts {} 158 | impl Opt for NCuts { 159 | const INDEX: usize = m::moptions_et_METIS_OPTION_NCUTS as usize; 160 | 161 | fn value(self) -> Idx { 162 | self.0 163 | } 164 | } 165 | 166 | /// Specifies the number of different separators that it will compute at each 167 | /// level of nested dissection. 168 | /// 169 | /// The final separator that is used is the smallest one. Default is 1. 170 | pub struct NSeps(pub Idx); 171 | 172 | impl private::Sealed for NSeps {} 173 | impl Opt for NSeps { 174 | const INDEX: usize = m::moptions_et_METIS_OPTION_NSEPS as usize; 175 | 176 | fn value(self) -> Idx { 177 | self.0 178 | } 179 | } 180 | 181 | /// Used to indicate which numbering scheme is used for the adjacency structure 182 | /// of a graph or the element-node structure of a mesh. 183 | #[allow(dead_code)] 184 | pub(crate) enum Numbering { 185 | /// C-style numbering which is assumed to start from 0. 186 | C, 187 | 188 | /// Fortran-style numbering which is assumed to start from 1. 189 | Fortran, 190 | } 191 | 192 | impl private::Sealed for Numbering {} 193 | impl Opt for Numbering { 194 | const INDEX: usize = m::moptions_et_METIS_OPTION_NUMBERING as usize; 195 | 196 | fn value(self) -> Idx { 197 | match self { 198 | Numbering::C => 0, 199 | Numbering::Fortran => 1, 200 | } 201 | } 202 | } 203 | 204 | /// Specifies the number of iterations for the refinement algorithms at each 205 | /// stage of the uncoarsening process. 206 | /// 207 | /// Default is 10. 208 | pub struct NIter(pub Idx); 209 | 210 | impl private::Sealed for NIter {} 211 | impl Opt for NIter { 212 | const INDEX: usize = m::moptions_et_METIS_OPTION_NITER as usize; 213 | 214 | fn value(self) -> Idx { 215 | self.0 216 | } 217 | } 218 | 219 | /// Specifies the seed for the random number generator. 220 | pub struct Seed(pub Idx); 221 | 222 | impl private::Sealed for Seed {} 223 | impl Opt for Seed { 224 | const INDEX: usize = m::moptions_et_METIS_OPTION_SEED as usize; 225 | 226 | fn value(self) -> Idx { 227 | self.0 228 | } 229 | } 230 | 231 | /// Specifies that the partitioning routines should try to minimize the maximum 232 | /// degree of the subdomain graph. 233 | /// 234 | /// I.e., the graph in which each partition is a node, and edges connect 235 | /// subdomains with a shared interface. 236 | pub struct MinConn(pub bool); 237 | 238 | impl private::Sealed for MinConn {} 239 | impl Opt for MinConn { 240 | const INDEX: usize = m::moptions_et_METIS_OPTION_MINCONN as usize; 241 | 242 | fn value(self) -> Idx { 243 | self.0 as Idx 244 | } 245 | } 246 | 247 | /// Specifies that the coarsening will not perform any 2-hop matching when the 248 | /// standards matching approach fails to sufficiently coarsen the graph. 249 | /// 250 | /// The 2-hop matching is very effective for graphs with power-law degree 251 | /// distributions. 252 | pub struct No2Hop(pub bool); 253 | 254 | impl private::Sealed for No2Hop {} 255 | impl Opt for No2Hop { 256 | const INDEX: usize = m::moptions_et_METIS_OPTION_NO2HOP as usize; 257 | 258 | fn value(self) -> Idx { 259 | self.0 as Idx 260 | } 261 | } 262 | 263 | /// Specifies that the partitioning routines should try to produce partitions 264 | /// that are contiguous. 265 | /// 266 | /// Note that if the input graph is not connected this option is ignored. 267 | pub struct Contig(pub bool); 268 | 269 | impl private::Sealed for Contig {} 270 | impl Opt for Contig { 271 | const INDEX: usize = m::moptions_et_METIS_OPTION_CONTIG as usize; 272 | 273 | fn value(self) -> Idx { 274 | self.0 as Idx 275 | } 276 | } 277 | 278 | /// Specifies that the graph should be compressed by combining vertices 279 | /// that have identical adjacency lists. 280 | pub struct Compress(pub bool); 281 | 282 | impl private::Sealed for Compress {} 283 | impl Opt for Compress { 284 | const INDEX: usize = m::moptions_et_METIS_OPTION_COMPRESS as usize; 285 | 286 | fn value(self) -> Idx { 287 | self.0 as Idx 288 | } 289 | } 290 | 291 | /// Specifies if the connected components of the graph should first be 292 | /// identified and ordered separately. 293 | pub struct CCOrder(pub bool); 294 | 295 | impl private::Sealed for CCOrder {} 296 | impl Opt for CCOrder { 297 | const INDEX: usize = m::moptions_et_METIS_OPTION_CCORDER as usize; 298 | 299 | fn value(self) -> Idx { 300 | self.0 as Idx 301 | } 302 | } 303 | 304 | /// Specifies the minimum degree of the vertices that will be ordered last. 305 | /// 306 | /// If the specified value is `x > 0`, then any vertices with a degree greater 307 | /// than `0.1*x*(average degree)` are removed from the graph, an ordering of the 308 | /// rest of the vertices is computed, and an overall ordering is computed by 309 | /// ordering the removed vertices at the end of the overall ordering. For 310 | /// example if `x == 40`, and the average degree is 5, then the algorithm will 311 | /// remove all vertices with degree greater than 20. The vertices that are 312 | /// removed are ordered last (i.e., they are automatically placed in the 313 | /// top-level separator). Good values are often in the range of 60 to 200 (i.e., 314 | /// 6 to 20 times more than the average). Default value is 0, indicating that no 315 | /// vertices are removed. 316 | /// 317 | /// Used to control whether the ordering algorithm should remove any 318 | /// vertices with high degree (i.e., dense columns). This is particularly 319 | /// helpful for certain classes of LP matrices, in which there a few vertices 320 | /// that are connected to many other vertices. By removing these vertices prior 321 | /// to ordering, the quality and the amount of time required to do the ordering 322 | /// improves. 323 | pub struct PFactor(pub Idx); 324 | 325 | impl private::Sealed for PFactor {} 326 | impl Opt for PFactor { 327 | const INDEX: usize = m::moptions_et_METIS_OPTION_PFACTOR as usize; 328 | 329 | fn value(self) -> Idx { 330 | self.0 331 | } 332 | } 333 | 334 | /// Specifies the maximum allowed load imbalance among the partitions. 335 | /// 336 | /// A value of `x` indicates that the allowed load imbalance is `(1 + x)/1000`. 337 | /// The load imbalance for the `j`th constraint is defined to be 338 | /// `max_i(w[j,i]/t[j,i])`, where `w[j,i]` is the fraction of the overall 339 | /// weight of the `j`th constraint that is assigned to the`i`th partition and 340 | /// `t[j,i]` is the desired target weight of the `j`th constraint for the `i`th 341 | /// partition (i.e., that specified via `-tpwgts`). For `-ptype=rb`, the default 342 | /// value is 1 (i.e., load imbalance of 1.001) and for `-ptype=kway`, the 343 | /// default value is 30 (i.e., load imbalance of 1.03). 344 | pub struct UFactor(pub Idx); 345 | 346 | impl private::Sealed for UFactor {} 347 | impl Opt for UFactor { 348 | const INDEX: usize = m::moptions_et_METIS_OPTION_UFACTOR as usize; 349 | 350 | fn value(self) -> Idx { 351 | self.0 352 | } 353 | } 354 | 355 | /// Specifies the amount of progress/debugging information will be printed 356 | /// during the execution of the algorithms. 357 | /// 358 | /// The default value is false for every field (no debugging/progress 359 | /// information). 360 | pub struct DbgLvl { 361 | /// Prints various diagnostic messages. 362 | pub info: bool, 363 | 364 | /// Performs timing analysis. 365 | pub time: bool, 366 | 367 | /// Displays various statistics during coarsening. 368 | pub coarsen: bool, 369 | 370 | /// Displays various statistics during refinement. 371 | pub refine: bool, 372 | 373 | /// Displays various statistics during initial partitioning. 374 | pub ipart: bool, 375 | 376 | /// Display detailed information about vertex moves during refinement. 377 | pub move_info: bool, 378 | 379 | /// Display detailed information about vertex separators. 380 | pub sep_info: bool, 381 | 382 | /// Display information related to the minimization of subdomain 383 | /// connectivity. 384 | pub conn_info: bool, 385 | 386 | /// Display information related to the elimination of connected components. 387 | pub contig_info: bool, 388 | } 389 | 390 | impl private::Sealed for DbgLvl {} 391 | impl Opt for DbgLvl { 392 | const INDEX: usize = m::moptions_et_METIS_OPTION_DBGLVL as usize; 393 | 394 | fn value(self) -> Idx { 395 | let mut dbglvl = 0; 396 | if self.info { 397 | dbglvl |= 1; 398 | } 399 | if self.time { 400 | dbglvl |= 2; 401 | } 402 | if self.coarsen { 403 | dbglvl |= 4; 404 | } 405 | if self.refine { 406 | dbglvl |= 8; 407 | } 408 | if self.ipart { 409 | dbglvl |= 16; 410 | } 411 | if self.move_info { 412 | dbglvl |= 32; 413 | } 414 | if self.sep_info { 415 | dbglvl |= 64; 416 | } 417 | if self.conn_info { 418 | dbglvl |= 128; 419 | } 420 | if self.contig_info { 421 | dbglvl |= 256; 422 | } 423 | dbglvl 424 | } 425 | } 426 | --------------------------------------------------------------------------------