Skip to main content

mt_kahypar/
lib.rs

1//! # mt-kahypar — (Non-official) Static & Safe Rust bindings for Mt‑KaHyPar
2//!
3//! **mt-kahypar** provides an idiomatic, ownership‑aware interface to the
4//! high‑performance C++ *Mt‑KaHyPar* (multi‑level hypergraph partitioner)
5//! library.
6//!
7//! **mt-kahypar** compiles and links *Mt-KaHyPar* and all its dependencies
8//! (Boost and TBB) statically inside special namespaces.
9//!
10//! ---
11//!
12//! ## Add the dependency
13//!
14//! ```toml
15//! [dependencies]
16//! mt-kahypar = "0.2"
17//! ```
18//!
19//! ## Quick start
20//!
21//! ```no_run
22//! use mt_kahypar::*;
23//!
24//! // 1. Build a partitioning context.
25//! let ctx = Context::builder()
26//!     .preset(Preset::Deterministic)
27//!     .k(4)                 // number of blocks
28//!     .epsilon(0.03)        // 3 % imbalance
29//!     .objective(Objective::Km1)
30//!     .seed(42)
31//!     .verbose(false)       // change to true to print detailed logs
32//!     .build()?;
33//!
34//! // 2. Load (or construct) the hypergraph to be partitioned.
35//! let hg = Hypergraph::from_file("netlist.hgr", &ctx, FileFormat::HMetis)?;
36//!
37//! // 3. Partition it.
38//! let part = hg.partition()?;
39//!
40//! println!("cut = {} | imbalance = {}%", part.cut(), part.imbalance()*100.0);
41//! # Ok::<(), mt_kahypar::Error>(())
42//! ```
43//!
44//! ## Thread‑pool control
45//! The very first `Context` creation implicitly calls [`initialize_default`],
46//! spawning an Mt‑KaHyPar thread pool with as many threads as logical CPUs.
47//! If you need finer control invoke [`initialize`] *once* **before** any other
48//! call:
49//!
50//! ```no_run
51//! mt_kahypar::initialize(64, /* interleaved = */ true);
52//! ```
53//!
54//! ## Design notes & safety
55//! * All FFI handles (`Context`, `Hypergraph`, …) own their native resources
56//!   and free them via `Drop`.
57//! * Each handle stores an immutable reference to the `Context` it was created
58//!   with.  Mixing objects built from *different* contexts triggers an
59//!   assertion at the point of use.
60//!
61//! ---
62//! For more details see the docs of individual types below or the upstream
63//! [Mt‑KaHyPar README](https://github.com/gzz2000/mt-kahypar-sc).
64
65/// The raw C bindings for Mt-KaHyPar.
66///
67/// Generated by the following command.
68/// ``` sh
69/// bindgen mt-kahypar-sc/include/mtkahypar.h --default-enum-style rust_non_exhaustive --rust-edition 2021 --rust-target 1.73.0 > src/raw.rs
70/// ```
71#[allow(non_camel_case_types, non_upper_case_globals)]
72pub mod sys;
73
74use std::{
75    ffi::{CStr, CString},
76    ptr,
77    sync::Once,
78};
79
80static INIT: Once = Once::new();
81
82/// Manual global initialization (optional) of thread pools.
83///
84/// * `num_threads` — maximum number of worker threads Mt‑KaHyPar should spawn.
85/// * `interleaved` — whether NUMA interleaved allocation should be enabled.
86///
87/// It is safe to call this at most **once** and *before* any `Context` is
88/// created.  Subsequent calls are silently ignored.
89pub fn initialize(num_threads: usize, interleaved: bool) {
90    INIT.call_once(|| unsafe {
91        sys::mt_kahypar_initialize(num_threads, interleaved);
92    });
93}
94
95/// Like [`initialize`] but automatically picks `num_threads` equal to the
96/// number of logical CPUs.
97pub fn initialize_default() {
98    let num_threads = std::thread::available_parallelism()
99        .map(|n| n.get())
100        .unwrap_or(1);
101    initialize(num_threads, true);
102}
103
104fn ensure_initialized() {
105    if !INIT.is_completed() {
106        let _ = initialize_default();
107    }
108}
109
110/// Library‑level error wrapper returned by most fallible API calls.
111#[derive(Debug)]
112pub struct Error {
113    pub status: Status,
114    pub message: String,
115}
116
117impl std::fmt::Display for Error {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        write!(f, "{:?}: {}", self.status, self.message)
120    }
121}
122impl std::error::Error for Error {}
123
124pub type Result<T, E = Error> = std::result::Result<T, E>;
125
126/// Mirror of `mt_kahypar_status_t`.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum Status {
129    Success,
130    InvalidInput,
131    InvalidParameter,
132    UnsupportedOperation,
133    SystemError,
134    OtherError,
135}
136impl From<sys::mt_kahypar_status_t> for Status {
137    fn from(s: sys::mt_kahypar_status_t) -> Self {
138        use sys::mt_kahypar_status_t::*;
139        match s {
140            SUCCESS => Status::Success,
141            INVALID_INPUT => Status::InvalidInput,
142            INVALID_PARAMETER => Status::InvalidParameter,
143            UNSUPPORTED_OPERATION => Status::UnsupportedOperation,
144            SYSTEM_ERROR => Status::SystemError,
145            // non_exhaustive has no effect in this own crate.
146            OTHER_ERROR => Status::OtherError,
147        }
148    }
149}
150
151/// Objective functions.
152///
153/// See [upstream docs](https://github.com/gzz2000/mt-kahypar-sc/tree/master?tab=readme-ov-file#supported-objective-functions) for their descriptions.
154#[derive(Clone, Copy)]
155pub enum Objective {
156    /// Cut-Net Metric
157    Cut,
158    /// Connectivity Metric
159    Km1,
160    /// Sum-of-external-Degrees Metric
161    Soed,
162}
163impl From<Objective> for sys::mt_kahypar_objective_t {
164    fn from(o: Objective) -> Self {
165        match o {
166            Objective::Cut => sys::mt_kahypar_objective_t::CUT,
167            Objective::Km1 => sys::mt_kahypar_objective_t::KM1,
168            Objective::Soed => sys::mt_kahypar_objective_t::SOED,
169        }
170    }
171}
172
173/// High‑level preset *recipes* shipped with Mt‑KaHyPar.
174///
175/// The default is now `Deterministic` rather than legacy `Default`.
176#[derive(Clone, Copy, Default)]
177pub enum Preset {
178    /// Deterministic & repeatable runs.
179    #[default]
180    Deterministic,
181    /// Tweaked for large‑`k` (many blocks) instances.
182    LargeK,
183    /// Legacy default of the C API.
184    Default,
185    /// Better quality at higher runtime.
186    Quality,
187    /// Highest available quality settings.
188    HighestQuality,
189}
190impl From<Preset> for sys::mt_kahypar_preset_type_t {
191    fn from(p: Preset) -> Self {
192        use sys::mt_kahypar_preset_type_t::*;
193        match p {
194            Preset::Deterministic => DETERMINISTIC,
195            Preset::LargeK => LARGE_K,
196            Preset::Default => DEFAULT,
197            Preset::Quality => QUALITY,
198            Preset::HighestQuality => HIGHEST_QUALITY,
199        }
200    }
201}
202
203/// Supported on‑disk formats for [`Hypergraph::from_file`].
204#[derive(Clone, Copy)]
205pub enum FileFormat {
206    Metis,
207    HMetis,
208}
209impl From<FileFormat> for sys::mt_kahypar_file_format_type_t {
210    fn from(f: FileFormat) -> Self {
211        match f {
212            FileFormat::Metis => sys::mt_kahypar_file_format_type_t::METIS,
213            FileFormat::HMetis => sys::mt_kahypar_file_format_type_t::HMETIS,
214        }
215    }
216}
217
218/// Handle Mt-KaHyPar error struct <-> Rust error.
219unsafe fn check_status(status: sys::mt_kahypar_status_t, err: &mut sys::mt_kahypar_error_t) -> Result<()> {
220    if status == sys::mt_kahypar_status_t::SUCCESS {
221        return Ok(());
222    }
223    let msg = if !err.msg.is_null() {
224        CStr::from_ptr(err.msg).to_string_lossy().into_owned()
225    } else {
226        "<no error message>".into()
227    };
228    sys::mt_kahypar_free_error_content(err);
229    Err(Error {
230        status: status.into(),
231        message: msg,
232    })
233}
234
235/* ------------------------------------------------------------------------- */
236/* Context & Builder                                                         */
237/* ------------------------------------------------------------------------- */
238
239/// A **partitioning context** bundles *all* algorithmic parameters.
240pub struct Context {
241    raw: *mut sys::mt_kahypar_context_t,
242}
243unsafe impl Send for Context {}
244unsafe impl Sync for Context {}
245
246impl Drop for Context {
247    fn drop(&mut self) {
248        unsafe { sys::mt_kahypar_free_context(self.raw) };
249    }
250}
251
252impl Context {
253    /// Start building a [`Context`].
254    pub fn builder() -> ContextBuilder {
255        ContextBuilder::default()
256    }
257}
258
259/// Fluent builder for [`Context`].  Construct via [`Context::builder`].
260#[derive(Default)]
261pub struct ContextBuilder {
262    preset: Preset,
263    k: Option<i32>,
264    epsilon: Option<f64>,
265    objective: Option<Objective>,
266    seed: Option<usize>,
267    verbose: bool,
268}
269
270impl ContextBuilder {
271    /// Apply a preset in the beginning.
272    pub fn preset(mut self, p: Preset) -> Self {
273        self.preset = p;
274        self
275    }
276    /// The number of partitions.
277    pub fn k(mut self, k: i32) -> Self {
278        self.k = Some(k);
279        self
280    }
281    /// The epsilon value (1+epsilon imbalance).
282    pub fn epsilon(mut self, eps: f64) -> Self {
283        self.epsilon = Some(eps);
284        self
285    }
286    /// The objective function used
287    pub fn objective(mut self, obj: Objective) -> Self {
288        self.objective = Some(obj);
289        self
290    }
291    /// Random seed (for deterministic partitioning algorithms)
292    pub fn seed(mut self, seed: usize) -> Self {
293        self.seed = Some(seed);
294        self
295    }
296    /// Whether to print verbose partitioning logs to stderr.
297    pub fn verbose(mut self, v: bool) -> Self {
298        self.verbose = v;
299        self
300    }
301
302    /// Build the context.
303    pub fn build(self) -> Result<Context> {
304        ensure_initialized();
305
306        let raw_ctx =
307            unsafe { sys::mt_kahypar_context_from_preset(self.preset.into()) };
308        if raw_ctx.is_null() {
309            return Err(Error {
310                status: Status::SystemError,
311                message: "mt_kahypar_context_from_preset returned NULL".into(),
312            });
313        }
314
315        unsafe {
316            if let Some(seed) = self.seed {
317                sys::mt_kahypar_set_seed(seed);
318            }
319            if self.verbose {
320                let s = CString::new("1").unwrap();
321                let mut err = sys::mt_kahypar_error_t {
322                    msg: ptr::null(),
323                    msg_len: 0,
324                    status: sys::mt_kahypar_status_t::SUCCESS,
325                };
326                let st = sys::mt_kahypar_set_context_parameter(
327                    raw_ctx,
328                    sys::mt_kahypar_context_parameter_type_t::VERBOSE,
329                    s.as_ptr(),
330                    &mut err,
331                );
332                check_status(st, &mut err)?;
333            }
334            if let (Some(k), Some(eps), Some(obj)) = (self.k, self.epsilon, self.objective) {
335                sys::mt_kahypar_set_partitioning_parameters(
336                    raw_ctx,
337                    k,
338                    eps,
339                    obj.into(),
340                );
341            }
342        }
343
344        Ok(Context { raw: raw_ctx })
345    }
346}
347
348/* ------------------------------------------------------------------------- */
349/* Hypergraph                                                                */
350/* ------------------------------------------------------------------------- */
351
352/// A (Hyper)graph to be partitioned.
353pub struct Hypergraph<'ctx> {
354    raw: sys::mt_kahypar_hypergraph_t,
355    ctx: &'ctx Context,
356    num_vertices: usize,
357}
358unsafe impl<'ctx> Send for Hypergraph<'ctx> {}
359unsafe impl<'ctx> Sync for Hypergraph<'ctx> {}
360
361impl<'ctx> Drop for Hypergraph<'ctx> {
362    fn drop(&mut self) {
363        unsafe { sys::mt_kahypar_free_hypergraph(self.raw) };
364    }
365}
366
367impl<'ctx> Hypergraph<'ctx> {
368    /// Load from file (Metis / hMetis).
369    ///
370    /// Note that we use different (hyper)graph data structures for different configurations.
371    /// Make sure that you partition the hypergraph with the same configuration as it is loaded.
372    pub fn from_file(path: &str, ctx: &'ctx Context, format: FileFormat) -> Result<Self> {
373        ensure_initialized();
374        let c_path = CString::new(path).unwrap();
375        let mut err = sys::mt_kahypar_error_t {
376            msg: ptr::null(),
377            msg_len: 0,
378            status: sys::mt_kahypar_status_t::SUCCESS,
379        };
380        let hg = unsafe {
381            sys::mt_kahypar_read_hypergraph_from_file(
382                c_path.as_ptr(),
383                ctx.raw,
384                format.into(),
385                &mut err,
386            )
387        };
388        if hg.hypergraph.is_null() {
389            return Err(Error {
390                status: Status::InvalidInput,
391                message: unsafe {
392                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
393                    sys::mt_kahypar_free_error_content(&mut err);
394                    m
395                },
396            });
397        }
398        let n = unsafe { sys::mt_kahypar_num_hypernodes(hg) as usize };
399        Ok(Hypergraph {
400            raw: hg,
401            ctx,
402            num_vertices: n,
403        })
404    }
405
406    /// Constructs a hypergraph from a given adjacency array that specifies the hyperedges.
407    ///
408    /// For example:
409    /// ``` text
410    /// hyperedge_indices: | 0   | 2       | 6     | 9     | 12
411    /// hyperedges:        | 0 2 | 0 1 3 4 | 3 4 6 | 2 5 6 |
412    /// ```
413    /// Defines a hypergraph with four hyperedges, e.g., `e_0 = {0,2}, e_1 = {0,1,3,4}, ...`
414    /// `hyperedge_indices` **must** end with `hyperedges.len()`.
415    /// note: For unweighted hypergraphs, you can pass None to either hyperedge_weights or vertex_weights.
416    pub fn from_adjacency(
417        ctx: &'ctx Context,
418        num_vertices: usize,
419        hyperedge_indices: &[usize],
420        hyperedges: &[usize],
421        hyperedge_weights: Option<&[i32]>,
422        vertex_weights: Option<&[i32]>,
423    ) -> Result<Self> {
424        ensure_initialized();
425        assert_eq!(
426            hyperedge_indices.last().copied().unwrap_or(0),
427            hyperedges.len(),
428            "indices array must terminate with |E|",
429        );
430
431        let mut err = sys::mt_kahypar_error_t {
432            msg: ptr::null(),
433            msg_len: 0,
434            status: sys::mt_kahypar_status_t::SUCCESS,
435        };
436        let hg = unsafe {
437            sys::mt_kahypar_create_hypergraph(
438                ctx.raw,
439                num_vertices as _,
440                (hyperedge_indices.len() - 1) as _,
441                hyperedge_indices.as_ptr(),
442                hyperedges.as_ptr() as _,
443                hyperedge_weights
444                    .map_or(ptr::null(), |w| w.as_ptr())
445                    as *const sys::mt_kahypar_hyperedge_weight_t,
446                vertex_weights
447                    .map_or(ptr::null(), |w| w.as_ptr())
448                    as *const sys::mt_kahypar_hypernode_weight_t,
449                &mut err,
450            )
451        };
452        if hg.hypergraph.is_null() {
453            return Err(Error {
454                status: Status::InvalidInput,
455                message: unsafe {
456                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
457                    sys::mt_kahypar_free_error_content(&mut err);
458                    m
459                },
460            });
461        }
462        Ok(Hypergraph {
463            raw: hg,
464            ctx,
465            num_vertices,
466        })
467    }
468
469    /* ------------ Partitioning & Mapping ---------------- */
470
471    /// Partitions a (hyper)graph with the configuration specified in the partitioning context.
472    ///
473    /// Before partitioning, the number of blocks, imbalance parameter and objective function must be set in the partitioning context.
474    pub fn partition(&self) -> Result<PartitionedHypergraph<'ctx>> {
475        ensure_initialized();
476        let mut err = sys::mt_kahypar_error_t {
477            msg: ptr::null(),
478            msg_len: 0,
479            status: sys::mt_kahypar_status_t::SUCCESS,
480        };
481        let ctx = self.ctx;
482        let num_v = self.num_vertices;
483        let phg = unsafe { sys::mt_kahypar_partition(self.raw, ctx.raw, &mut err) };
484        if phg.partitioned_hg.is_null() {
485            return Err(Error {
486                status: Status::OtherError,
487                message: unsafe {
488                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
489                    sys::mt_kahypar_free_error_content(&mut err);
490                    m
491                },
492            });
493        }
494        Ok(PartitionedHypergraph {
495            raw: phg,
496            ctx,
497            num_vertices: num_v,
498        })
499    }
500
501    /// Map onto a target graph (Steiner-tree objective).
502    pub fn map(
503        &self,
504        target: &mut TargetGraph<'ctx>,
505    ) -> Result<PartitionedHypergraph<'ctx>> {
506        ensure_initialized();
507        assert_eq!(self.ctx.raw, target.ctx.raw, "context mismatch");
508        let mut err = sys::mt_kahypar_error_t {
509            msg: ptr::null(),
510            msg_len: 0,
511            status: sys::mt_kahypar_status_t::SUCCESS,
512        };
513        let ctx = self.ctx;
514        let num_v = self.num_vertices;
515        let phg = unsafe { sys::mt_kahypar_map(self.raw, target.raw, ctx.raw, &mut err) };
516        if phg.partitioned_hg.is_null() {
517            return Err(Error {
518                status: Status::OtherError,
519                message: unsafe {
520                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
521                    sys::mt_kahypar_free_error_content(&mut err);
522                    m
523                },
524            });
525        }
526        Ok(PartitionedHypergraph {
527            raw: phg,
528            ctx,
529            num_vertices: num_v,
530        })
531    }
532
533    /* ------------ Introspection ---------------- */
534
535    #[inline]
536    pub fn num_nodes(&self) -> usize {
537        self.num_vertices
538    }
539    #[inline]
540    pub fn num_edges(&self) -> usize {
541        unsafe { sys::mt_kahypar_num_hyperedges(self.raw) as usize }
542    }
543}
544
545/* ------------------------------------------------------------------------- */
546/* TargetGraph                                                               */
547/* ------------------------------------------------------------------------- */
548
549/// Target graph. See [`sys::mt_kahypar_map`].
550pub struct TargetGraph<'ctx> {
551    raw: *mut sys::mt_kahypar_target_graph_t,
552    ctx: &'ctx Context,
553}
554unsafe impl<'ctx> Send for TargetGraph<'ctx> {}
555unsafe impl<'ctx> Sync for TargetGraph<'ctx> {}
556
557impl<'ctx> Drop for TargetGraph<'ctx> {
558    fn drop(&mut self) {
559        unsafe { sys::mt_kahypar_free_target_graph(self.raw) };
560    }
561}
562
563impl<'ctx> TargetGraph<'ctx> {
564    /// Read from a Metis file.
565    pub fn from_file(path: &str, ctx: &'ctx Context) -> Result<Self> {
566        ensure_initialized();
567        let c_path = CString::new(path).unwrap();
568        let mut err = sys::mt_kahypar_error_t {
569            msg: ptr::null(),
570            msg_len: 0,
571            status: sys::mt_kahypar_status_t::SUCCESS,
572        };
573        let tg = unsafe {
574            sys::mt_kahypar_read_target_graph_from_file(
575                c_path.as_ptr(),
576                ctx.raw,
577                &mut err,
578            )
579        };
580        if tg.is_null() {
581            return Err(Error {
582                status: Status::InvalidInput,
583                message: unsafe {
584                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
585                    sys::mt_kahypar_free_error_content(&mut err);
586                    m
587                },
588            });
589        }
590        Ok(TargetGraph { raw: tg, ctx })
591    }
592
593    /// Build from raw edge list.
594    pub fn from_edges(
595        ctx: &'ctx Context,
596        num_vertices: usize,
597        edges: &[(usize, usize)],
598        edge_weights: Option<&[i32]>,
599    ) -> Result<Self> {
600        ensure_initialized();
601        let flat: Vec<usize> = edges.iter().flat_map(|&(u, v)| [u, v]).collect();
602        let mut err = sys::mt_kahypar_error_t {
603            msg: ptr::null(),
604            msg_len: 0,
605            status: sys::mt_kahypar_status_t::SUCCESS,
606        };
607        let tg = unsafe {
608            sys::mt_kahypar_create_target_graph(
609                ctx.raw,
610                num_vertices as _,
611                edges.len() as _,
612                flat.as_ptr() as _,
613                edge_weights
614                    .map_or(ptr::null(), |w| w.as_ptr())
615                    as *const sys::mt_kahypar_hyperedge_weight_t,
616                &mut err,
617            )
618        };
619        if tg.is_null() {
620            return Err(Error {
621                status: Status::InvalidInput,
622                message: unsafe {
623                    let m = CStr::from_ptr(err.msg).to_string_lossy().into_owned();
624                    sys::mt_kahypar_free_error_content(&mut err);
625                    m
626                },
627            });
628        }
629        Ok(TargetGraph { raw: tg, ctx })
630    }
631}
632
633/* ------------------------------------------------------------------------- */
634/* PartitionedHypergraph                                                     */
635/* ------------------------------------------------------------------------- */
636
637/// A partitioned hypergraph.
638pub struct PartitionedHypergraph<'ctx> {
639    raw: sys::mt_kahypar_partitioned_hypergraph_t,
640    ctx: &'ctx Context,
641    num_vertices: usize,
642}
643unsafe impl<'ctx> Send for PartitionedHypergraph<'ctx> {}
644unsafe impl<'ctx> Sync for PartitionedHypergraph<'ctx> {}
645
646impl<'ctx> Drop for PartitionedHypergraph<'ctx> {
647    fn drop(&mut self) {
648        unsafe { sys::mt_kahypar_free_partitioned_hypergraph(self.raw) };
649    }
650}
651
652impl<'ctx> PartitionedHypergraph<'ctx> {
653    /// Improves a given partition (using the V-cycle technique).
654    ///
655    /// note: There is no guarantee that this call will find an improvement.
656    pub fn improve(&mut self, num_vcycles: usize) -> Result<()> {
657        ensure_initialized();
658        let mut err = sys::mt_kahypar_error_t {
659            msg: ptr::null(),
660            msg_len: 0,
661            status: sys::mt_kahypar_status_t::SUCCESS,
662        };
663        let st = unsafe {
664            sys::mt_kahypar_improve_partition(self.raw, self.ctx.raw, num_vcycles, &mut err)
665        };
666        unsafe { check_status(st, &mut err) }
667    }
668
669    /// Improves a given mapping (using the V-cycle technique).
670    ///
671    /// note: The number of nodes of the target graph must be equal to the
672    /// number of blocks of the given partition.
673    ///
674    /// note: There is no guarantee that this call will find an improvement.
675    pub fn improve_mapping(
676        &mut self,
677        target: &mut TargetGraph<'ctx>,
678        num_vcycles: usize,
679    ) -> Result<()> {
680        ensure_initialized();
681        assert_eq!(self.ctx.raw, target.ctx.raw, "context mismatch");
682        let mut err = sys::mt_kahypar_error_t {
683            msg: ptr::null(),
684            msg_len: 0,
685            status: sys::mt_kahypar_status_t::SUCCESS,
686        };
687        let st = unsafe {
688            sys::mt_kahypar_improve_mapping(
689                self.raw,
690                target.raw,
691                self.ctx.raw,
692                num_vcycles,
693                &mut err,
694            )
695        };
696        unsafe { check_status(st, &mut err) }
697    }
698
699    /* ----- Metrics ----- */
700
701    #[inline]
702    pub fn imbalance(&self) -> f64 {
703        unsafe { sys::mt_kahypar_imbalance(self.raw, self.ctx.raw) }
704    }
705    #[inline]
706    pub fn cut(&self) -> i32 {
707        unsafe { sys::mt_kahypar_cut(self.raw) }
708    }
709    #[inline]
710    pub fn km1(&self) -> i32 {
711        unsafe { sys::mt_kahypar_km1(self.raw) }
712    }
713    #[inline]
714    pub fn soed(&self) -> i32 {
715        unsafe { sys::mt_kahypar_soed(self.raw) }
716    }
717
718    #[inline]
719    pub fn num_blocks(&self) -> i32 {
720        unsafe { sys::mt_kahypar_num_blocks(self.raw) }
721    }
722
723    /// Returns a `Vec` with partition ids, length = #nodes.
724    pub fn extract_partition(&self) -> Vec<i32> {
725        let mut part = vec![0; self.num_vertices];
726        unsafe { sys::mt_kahypar_get_partition(self.raw, part.as_mut_ptr()) };
727        part
728    }
729
730    /// Returns per-block weights.
731    pub fn block_weights(&self) -> Vec<i32> {
732        let n = self.num_blocks() as usize;
733        let mut bw = vec![0; n];
734        unsafe { sys::mt_kahypar_get_block_weights(self.raw, bw.as_mut_ptr()) };
735        bw
736    }
737}