1#[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
82pub fn initialize(num_threads: usize, interleaved: bool) {
90 INIT.call_once(|| unsafe {
91 sys::mt_kahypar_initialize(num_threads, interleaved);
92 });
93}
94
95pub 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#[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#[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 OTHER_ERROR => Status::OtherError,
147 }
148 }
149}
150
151#[derive(Clone, Copy)]
155pub enum Objective {
156 Cut,
158 Km1,
160 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#[derive(Clone, Copy, Default)]
177pub enum Preset {
178 #[default]
180 Deterministic,
181 LargeK,
183 Default,
185 Quality,
187 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#[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
218unsafe 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
235pub 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 pub fn builder() -> ContextBuilder {
255 ContextBuilder::default()
256 }
257}
258
259#[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 pub fn preset(mut self, p: Preset) -> Self {
273 self.preset = p;
274 self
275 }
276 pub fn k(mut self, k: i32) -> Self {
278 self.k = Some(k);
279 self
280 }
281 pub fn epsilon(mut self, eps: f64) -> Self {
283 self.epsilon = Some(eps);
284 self
285 }
286 pub fn objective(mut self, obj: Objective) -> Self {
288 self.objective = Some(obj);
289 self
290 }
291 pub fn seed(mut self, seed: usize) -> Self {
293 self.seed = Some(seed);
294 self
295 }
296 pub fn verbose(mut self, v: bool) -> Self {
298 self.verbose = v;
299 self
300 }
301
302 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
348pub 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 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 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 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 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 #[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
545pub 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 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 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
633pub 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 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 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 #[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 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 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}