diff --git a/Cargo.lock b/Cargo.lock index 76c7ead9..e3452913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,15 @@ dependencies = [ "sprs", ] +[[package]] +name = "coupe-metis" +version = "0.1.0" +dependencies = [ + "coupe", + "libc", + "sprs", +] + [[package]] name = "coupe-tools" version = "0.1.0" @@ -565,8 +574,6 @@ dependencies = [ [[package]] name = "metis" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969c4e429931766d84c38226ddade79db95dfd71ca85ebae2995ddc97369655c" dependencies = [ "metis-sys", ] @@ -574,8 +581,6 @@ dependencies = [ [[package]] name = "metis-sys" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab50927ac85eab34371c8caa5a13c1e2e308db6b125e167608d87037c167bd7b" dependencies = [ "bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index b405198b..64747ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ ".", + "coupe-metis", "ffi", "tools", "tools/mesh-io", diff --git a/coupe-metis/Cargo.toml b/coupe-metis/Cargo.toml new file mode 100644 index 00000000..9421689c --- /dev/null +++ b/coupe-metis/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "coupe-metis" +version = "0.1.0" +edition = "2021" + + +[lib] +name = "coupemetis" +crate-type = ["cdylib", "staticlib"] + + +[dependencies] +coupe = { version = "0.1", path = ".." } +libc = "0.2" +sprs = { version = "0.11", default-features = false } diff --git a/coupe-metis/README.md b/coupe-metis/README.md new file mode 100644 index 00000000..115ad683 --- /dev/null +++ b/coupe-metis/README.md @@ -0,0 +1,23 @@ +# libcoupemetis.so + +This library is an implementation of [MeTiS] 5.1 using coupe's algorithms. + +[MeTiS]: http://glaros.dtc.umn.edu/gkhome/metis/metis/overview + +## Building + +Build it with cargo. It will produce a shared object and an archive for static +linking. + +## Usage + +This library is a drop-in replacement for `libmetis.so`. Simply replace +`-lmetis` by `-lcoupemetis` in your linker flags: + +```shell +# Change this +cc -o my_program -c main.c -lmetis + +# To this +cc -o my_program -c main.c -lcoupemetis +``` diff --git a/coupe-metis/src/lib.rs b/coupe-metis/src/lib.rs new file mode 100644 index 00000000..387d7f6c --- /dev/null +++ b/coupe-metis/src/lib.rs @@ -0,0 +1,143 @@ +#![allow(clippy::missing_safety_doc)] // See the MeTiS manual + +use coupe::Partition as _; +use std::slice; + +pub type Idx = i32; +pub type Real = f32; + +//const NOPTIONS: usize = 40; // TODO + +#[repr(C)] +pub enum RStatus { + Ok = 1, + ErrorInput = -2, + ErrorMemory = -3, + Error = -4, +} + +#[allow(clippy::too_many_arguments)] +unsafe fn partition( + nvtxs: *const Idx, + ncon: *const Idx, + xadj: *const Idx, + adjncy: *const Idx, + vwgt: *const Idx, + _vsize: *const Idx, // TODO + adjwgt: *const Idx, + nparts: *const Idx, + _tpwgts: *const Idx, // TODO + _ubvec: *const Real, // TODO + _options: *const Idx, // TODO + objval: *mut Idx, + part: *mut Idx, +) -> RStatus { + let nvtxs = *nvtxs as usize; + let ncon = *ncon as usize; + if ncon != 1 { + // TODO make graph algorithms generic over input collection. + return RStatus::Error; + } + let nparts = *nparts as usize; + + // TODO make graph algorithms work over CsMatViewI instead of CsMatView + // to avoid these conversions. + let xadj: Vec = slice::from_raw_parts(xadj, nvtxs + 1) + .iter() + .map(|i| *i as usize) + .collect(); + let adjncy: Vec = slice::from_raw_parts(adjncy, xadj[xadj.len() - 1] as usize) + .iter() + .map(|i| *i as usize) + .collect(); + let adjwgt = if adjwgt.is_null() { + vec![1_i64; adjncy.len()] + } else { + // TODO make graph algorithms generic over weight type + slice::from_raw_parts(adjwgt, adjncy.len()) + .iter() + .map(|wgt| *wgt as i64) + .collect() + }; + let adjacency = sprs::CsMat::new((nvtxs, nvtxs), xadj, adjncy, adjwgt); + + let mut partition = vec![0; nvtxs]; + + let vwgt = if vwgt.is_null() { + vec![0.0; nvtxs] + } else { + // TODO make graph algorithms generic over weight type + slice::from_raw_parts(vwgt, nvtxs) + .iter() + .map(|wgt| *wgt as f64) + .collect() + }; + + let ret = coupe::KarmarkarKarp { part_count: nparts } + .partition(&mut partition, vwgt.iter().map(|w| coupe::Real::from(*w))); + if let Err(err) = ret { + println!("error: {}", err); + return RStatus::Error; + } + + let ret = + coupe::FiducciaMattheyses::default().partition(&mut partition, (adjacency.view(), &vwgt)); + if let Err(err) = ret { + println!("error: {}", err); + return RStatus::Error; + } + + let part = slice::from_raw_parts_mut(part, nvtxs); + for (dst, src) in part.iter_mut().zip(&mut partition) { + *dst = *src as Idx; + } + + // TODO have fidducia return the cut size. + *objval = 0; + + RStatus::Ok +} + +#[no_mangle] +pub unsafe extern "C" fn METIS_PartGraphKway( + nvtxs: *const Idx, + ncon: *const Idx, + xadj: *const Idx, + adjncy: *const Idx, + vwgt: *const Idx, + vsize: *const Idx, + adjwgt: *const Idx, + nparts: *const Idx, + tpwgts: *const Idx, + ubvec: *const Real, + options: *const Idx, + objval: *mut Idx, + part: *mut Idx, +) -> RStatus { + partition( + nvtxs, ncon, xadj, adjncy, vwgt, vsize, adjwgt, nparts, tpwgts, ubvec, options, objval, + part, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn METIS_PartGraphRecursive( + nvtxs: *const Idx, + ncon: *const Idx, + xadj: *const Idx, + adjncy: *const Idx, + vwgt: *const Idx, + vsize: *const Idx, + adjwgt: *const Idx, + nparts: *const Idx, + tpwgts: *const Idx, + ubvec: *const Real, + options: *const Idx, + objval: *mut Idx, + part: *mut Idx, +) -> RStatus { + partition( + nvtxs, ncon, xadj, adjncy, vwgt, vsize, adjwgt, nparts, tpwgts, ubvec, options, objval, + part, + ) +} diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 54a0a8d7..41303553 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -20,7 +20,7 @@ default = ["scotch", "metis"] # Partitioners coupe = { version = "0.1", path = ".." } scotch = { version = "0.1", optional = true } -metis = { version = "0.1", optional = true } +metis = { version = "0.1", optional = true, path = "../../metis-rs" } # Random number generation rand = { version = "0.8", default-features = false, features = ["std"] }