1#![allow(non_camel_case_types)]
2
3use std::ffi::{CStr, CString, c_char};
4use std::panic::AssertUnwindSafe;
5use std::ptr;
6use std::sync::OnceLock;
7
8use howzat_kit::{Backend, BackendGeometry, BackendRunAny, BackendRunConfig, Representation, Stats};
9use howzat_kit::backend::{AnyPolytopeCoefficients, CoefficientMatrix, RowMajorMatrix};
10use hullabaloo::AdjacencyList;
11use hullabaloo::set_family::{ListFamily, SetFamily};
12
13const DEFAULT_BACKEND_SPEC: &str = "howzat-dd[purify[snap]]:f64[eps[1e-12]]";
14static DEFAULT_BACKEND: OnceLock<Backend> = OnceLock::new();
15
16const DEFAULT_EXACT_BACKEND_SPEC: &str = "howzat-dd:gmprat";
17static DEFAULT_EXACT_BACKEND: OnceLock<Backend> = OnceLock::new();
18
19fn default_backend() -> &'static Backend {
20 DEFAULT_BACKEND.get_or_init(|| {
21 DEFAULT_BACKEND_SPEC
22 .parse()
23 .expect("default backend spec must parse")
24 })
25}
26
27fn default_exact_backend() -> &'static Backend {
28 DEFAULT_EXACT_BACKEND.get_or_init(|| {
29 DEFAULT_EXACT_BACKEND_SPEC
30 .parse()
31 .expect("default exact backend spec must parse")
32 })
33}
34
35pub struct howzat_backend_t;
36
37pub struct howzat_result_t;
38
39pub struct howzat_dense_graph_t;
40
41pub struct howzat_adjacency_list_t;
42
43#[repr(C)]
44pub struct howzat_error_t {
45 message: *mut c_char,
46}
47
48#[repr(C)]
49pub struct howzat_usize_slice_t {
50 pub ptr: *const usize,
51 pub len: usize,
52}
53
54#[repr(C)]
55pub struct howzat_f64_slice_t {
56 pub ptr: *const f64,
57 pub len: usize,
58}
59
60#[repr(C)]
61pub struct howzat_i64_slice_t {
62 pub ptr: *const i64,
63 pub len: usize,
64}
65
66#[repr(C)]
67pub struct howzat_str_slice_t {
68 pub ptr: *const *const c_char,
69 pub len: usize,
70}
71
72#[allow(non_camel_case_types)]
73pub struct __mpq_struct;
75
76#[allow(non_camel_case_types)]
77pub type mpq_srcptr = *const __mpq_struct;
79
80#[repr(C)]
81pub struct howzat_mpq_slice_t {
82 pub ptr: *const mpq_srcptr,
83 pub len: usize,
84}
85
86#[derive(Copy, Clone, Debug, Eq, PartialEq)]
87#[repr(C)]
88pub enum howzat_graph_kind_t {
89 HOWZAT_GRAPH_DENSE = 0,
90 HOWZAT_GRAPH_SPARSE = 1,
91}
92
93#[derive(Copy, Clone, Debug, Eq, PartialEq)]
94#[repr(C)]
95pub enum howzat_representation_t {
96 HOWZAT_REPR_EUCLIDEAN_VERTICES = 0,
97 HOWZAT_REPR_INEQUALITY = 1,
98 HOWZAT_REPR_HOMOGENEOUS_GENERATORS = 2,
99}
100
101#[derive(Copy, Clone, Debug, Eq, PartialEq)]
102#[repr(C)]
103pub enum howzat_coeff_kind_t {
104 HOWZAT_COEFF_F64 = 0,
105 HOWZAT_COEFF_GMPRAT = 1,
106}
107
108struct BackendHandle {
109 inner: Backend,
110 spec: CString,
111}
112
113struct DenseGraphHandle {
114 inner: SetFamily,
115}
116
117struct AdjacencyListHandle {
118 inner: AdjacencyList,
119}
120
121enum GraphHandle {
122 Dense(DenseGraphHandle),
123 Sparse(AdjacencyListHandle),
124}
125
126struct ResultHandle {
127 spec: CString,
128 stats: Stats,
129 total_seconds: f64,
130 fails: usize,
131 fallbacks: usize,
132 vertex_positions: Option<Vec<f64>>,
133 vertex_positions_count: usize,
134 vertex_positions_dim: usize,
135 vertex_adjacency: GraphHandle,
136 facet_adjacency: GraphHandle,
137 facets_to_vertices_offsets: Vec<usize>,
138 facets_to_vertices_data: Vec<usize>,
139 coefficients: CoefficientsHandle,
140}
141
142struct CoeffMatrixF64Handle {
143 rows: usize,
144 cols: usize,
145 data: Vec<f64>,
146}
147
148struct CoeffMatrixI64Handle {
149 data: Vec<i64>,
150}
151
152struct CoeffMatrixStrHandle {
153 #[allow(dead_code)]
154 data: Vec<CString>,
155 ptrs: Vec<*const c_char>,
156}
157
158struct CoeffMatrixRugRatHandle {
159 rows: usize,
160 cols: usize,
161 data: Vec<calculo::num::RugRat>,
162 ptrs: Vec<mpq_srcptr>,
163 strings: OnceLock<CoeffMatrixStrHandle>,
164 i64s: OnceLock<Option<CoeffMatrixI64Handle>>,
165}
166
167enum CoefficientsHandle {
168 F64 {
169 generators: CoeffMatrixF64Handle,
170 inequalities: CoeffMatrixF64Handle,
171 },
172 RugRat {
173 generators: CoeffMatrixRugRatHandle,
174 inequalities: CoeffMatrixRugRatHandle,
175 },
176}
177
178fn cstring_lossy(s: &str) -> CString {
179 if !s.as_bytes().contains(&b'\0') {
180 return CString::new(s).expect("string must not contain NUL");
181 }
182 let sanitized: String = s.chars().map(|c| if c == '\0' { '�' } else { c }).collect();
183 CString::new(sanitized).expect("sanitized string must not contain NUL")
184}
185
186fn set_error(out: *mut *mut howzat_error_t, msg: &str) {
187 if out.is_null() {
188 return;
189 }
190 let message = cstring_lossy(msg).into_raw();
191 let err = Box::new(howzat_error_t { message });
192 unsafe {
193 *out = Box::into_raw(err);
194 }
195}
196
197fn clear_error(out: *mut *mut howzat_error_t) {
198 if out.is_null() {
199 return;
200 }
201 unsafe {
202 let prev = *out;
203 *out = ptr::null_mut();
204 if !prev.is_null() {
205 let prev = Box::from_raw(prev);
206 if !prev.message.is_null() {
207 drop(CString::from_raw(prev.message));
208 }
209 }
210 }
211}
212
213unsafe fn ref_backend<'a>(backend: *const howzat_backend_t) -> Option<&'a BackendHandle> {
214 if backend.is_null() {
215 return None;
216 }
217 Some(unsafe { &*(backend.cast::<BackendHandle>()) })
218}
219
220unsafe fn ref_result<'a>(result: *const howzat_result_t) -> Option<&'a ResultHandle> {
221 if result.is_null() {
222 return None;
223 }
224 Some(unsafe { &*(result.cast::<ResultHandle>()) })
225}
226
227unsafe fn ref_dense_graph<'a>(graph: *const howzat_dense_graph_t) -> Option<&'a DenseGraphHandle> {
228 if graph.is_null() {
229 return None;
230 }
231 Some(unsafe { &*(graph.cast::<DenseGraphHandle>()) })
232}
233
234unsafe fn ref_sparse_graph<'a>(
235 graph: *const howzat_adjacency_list_t,
236) -> Option<&'a AdjacencyListHandle> {
237 if graph.is_null() {
238 return None;
239 }
240 Some(unsafe { &*(graph.cast::<AdjacencyListHandle>()) })
241}
242
243fn flatten_set_family(family: &SetFamily) -> (Vec<usize>, Vec<usize>) {
244 let sets = family.sets();
245 let mut offsets = Vec::with_capacity(sets.len() + 1);
246 offsets.push(0);
247
248 let mut total = 0usize;
249 for set in sets {
250 total = total.saturating_add(set.cardinality());
251 offsets.push(total);
252 }
253
254 let mut data = Vec::with_capacity(total);
255 for set in sets {
256 data.extend(set.iter().raw());
257 }
258 (offsets, data)
259}
260
261fn flatten_list_family(family: &ListFamily) -> (Vec<usize>, Vec<usize>) {
262 let sets = family.sets();
263 let mut offsets = Vec::with_capacity(sets.len() + 1);
264 offsets.push(0);
265
266 let mut total = 0usize;
267 for set in sets {
268 total = total.saturating_add(set.len());
269 offsets.push(total);
270 }
271
272 let mut data = Vec::with_capacity(total);
273 for set in sets {
274 data.extend_from_slice(set);
275 }
276 (offsets, data)
277}
278
279fn build_coefficients_handle(
280 coefficients: Option<AnyPolytopeCoefficients>,
281) -> Result<CoefficientsHandle, String> {
282 let coefficients = coefficients.ok_or_else(|| "backend did not return coefficients".to_string())?;
283
284 fn build_rugrat_matrix(m: RowMajorMatrix<calculo::num::RugRat>) -> CoeffMatrixRugRatHandle {
285 let RowMajorMatrix { rows, cols, data } = m;
286 let ptrs = data
287 .iter()
288 .map(|v| v.0.as_raw().cast::<__mpq_struct>())
289 .collect();
290 CoeffMatrixRugRatHandle {
291 rows,
292 cols,
293 data,
294 ptrs,
295 strings: OnceLock::new(),
296 i64s: OnceLock::new(),
297 }
298 }
299
300 fn is_rational(matrix: &CoefficientMatrix) -> bool {
301 matches!(
302 matrix,
303 CoefficientMatrix::RugRat(_) | CoefficientMatrix::DashuRat(_)
304 )
305 }
306
307 match (coefficients.generators, coefficients.inequalities) {
308 (g, h) if !is_rational(&g) && !is_rational(&h) => {
309 let g = g
310 .coerce::<f64>()
311 .map_err(|_| "coefficient matrix could not be coerced to f64".to_string())?;
312 let h = h
313 .coerce::<f64>()
314 .map_err(|_| "coefficient matrix could not be coerced to f64".to_string())?;
315 Ok(CoefficientsHandle::F64 {
316 generators: CoeffMatrixF64Handle {
317 rows: g.rows,
318 cols: g.cols,
319 data: g.data,
320 },
321 inequalities: CoeffMatrixF64Handle {
322 rows: h.rows,
323 cols: h.cols,
324 data: h.data,
325 },
326 })
327 }
328 (g, h) if is_rational(&g) && is_rational(&h) => {
329 let g = g.coerce::<calculo::num::RugRat>().map_err(|_| {
330 "coefficient matrix could not be coerced to rug::Rational".to_string()
331 })?;
332 let h = h.coerce::<calculo::num::RugRat>().map_err(|_| {
333 "coefficient matrix could not be coerced to rug::Rational".to_string()
334 })?;
335 Ok(CoefficientsHandle::RugRat {
336 generators: build_rugrat_matrix(g),
337 inequalities: build_rugrat_matrix(h),
338 })
339 }
340 _ => Err("mixed coefficient kinds are not supported".to_string()),
341 }
342}
343
344fn build_result_any(run: BackendRunAny) -> Result<ResultHandle, String> {
345 match run {
346 BackendRunAny::Dense(run) => {
347 let howzat_kit::BackendRun {
348 spec,
349 stats,
350 timing,
351 geometry,
352 coefficients,
353 fails,
354 fallbacks,
355 error,
356 ..
357 } = run;
358
359 if let Some(err) = error {
360 return Err(err);
361 }
362
363 let vertex_positions_count = stats.vertices;
364 let vertex_positions_dim = stats.dimension;
365
366 let (vertex_positions, vertex_adjacency, facets_to_vertices, facet_adjacency) =
367 match geometry {
368 BackendGeometry::Baseline(b) => (
369 Some(b.vertex_positions),
370 b.vertex_adjacency,
371 b.facets_to_vertices,
372 b.facet_adjacency,
373 ),
374 BackendGeometry::Input(g) => (
375 None,
376 g.vertex_adjacency,
377 g.facets_to_vertices,
378 g.facet_adjacency,
379 ),
380 };
381
382 let (facets_to_vertices_offsets, facets_to_vertices_data) =
383 flatten_set_family(&facets_to_vertices);
384
385 let coefficients = build_coefficients_handle(coefficients)?;
386
387 let vertex_positions = match vertex_positions {
388 None => None,
389 Some(m) => Some(
390 m.coerce::<f64>()
391 .map_err(|_| "vertex positions could not be coerced to f64".to_string())?
392 .data,
393 ),
394 };
395
396 Ok(ResultHandle {
397 spec: cstring_lossy(&spec.to_string()),
398 stats,
399 total_seconds: timing.total.as_secs_f64(),
400 fails,
401 fallbacks,
402 vertex_positions,
403 vertex_positions_count,
404 vertex_positions_dim,
405 vertex_adjacency: GraphHandle::Dense(DenseGraphHandle {
406 inner: vertex_adjacency,
407 }),
408 facet_adjacency: GraphHandle::Dense(DenseGraphHandle {
409 inner: facet_adjacency,
410 }),
411 facets_to_vertices_offsets,
412 facets_to_vertices_data,
413 coefficients,
414 })
415 }
416 BackendRunAny::Sparse(run) => {
417 let howzat_kit::BackendRun {
418 spec,
419 stats,
420 timing,
421 geometry,
422 coefficients,
423 fails,
424 fallbacks,
425 error,
426 ..
427 } = run;
428
429 if let Some(err) = error {
430 return Err(err);
431 }
432
433 let vertex_positions_count = stats.vertices;
434 let vertex_positions_dim = stats.dimension;
435
436 let (vertex_positions, vertex_adjacency, facets_to_vertices, facet_adjacency) =
437 match geometry {
438 BackendGeometry::Baseline(b) => (
439 Some(b.vertex_positions),
440 b.vertex_adjacency,
441 b.facets_to_vertices,
442 b.facet_adjacency,
443 ),
444 BackendGeometry::Input(g) => (
445 None,
446 g.vertex_adjacency,
447 g.facets_to_vertices,
448 g.facet_adjacency,
449 ),
450 };
451
452 let (facets_to_vertices_offsets, facets_to_vertices_data) =
453 flatten_list_family(&facets_to_vertices);
454
455 let coefficients = build_coefficients_handle(coefficients)?;
456
457 let vertex_positions = match vertex_positions {
458 None => None,
459 Some(m) => Some(
460 m.coerce::<f64>()
461 .map_err(|_| "vertex positions could not be coerced to f64".to_string())?
462 .data,
463 ),
464 };
465
466 Ok(ResultHandle {
467 spec: cstring_lossy(&spec.to_string()),
468 stats,
469 total_seconds: timing.total.as_secs_f64(),
470 fails,
471 fallbacks,
472 vertex_positions,
473 vertex_positions_count,
474 vertex_positions_dim,
475 vertex_adjacency: GraphHandle::Sparse(AdjacencyListHandle {
476 inner: vertex_adjacency,
477 }),
478 facet_adjacency: GraphHandle::Sparse(AdjacencyListHandle {
479 inner: facet_adjacency,
480 }),
481 facets_to_vertices_offsets,
482 facets_to_vertices_data,
483 coefficients,
484 })
485 }
486 }
487}
488
489#[unsafe(no_mangle)]
490pub unsafe extern "C" fn howzat_error_message(err: *const howzat_error_t) -> *const c_char {
491 if err.is_null() {
492 return ptr::null();
493 }
494 unsafe { (*err).message }
495}
496
497#[unsafe(no_mangle)]
498pub unsafe extern "C" fn howzat_error_free(err: *mut howzat_error_t) {
499 if err.is_null() {
500 return;
501 }
502 unsafe {
503 let err = Box::from_raw(err);
504 if !err.message.is_null() {
505 drop(CString::from_raw(err.message));
506 }
507 }
508}
509
510#[unsafe(no_mangle)]
511pub unsafe extern "C" fn howzat_backend_new(
512 spec: *const c_char,
513 out_err: *mut *mut howzat_error_t,
514) -> *mut howzat_backend_t {
515 clear_error(out_err);
516
517 let make = || -> Result<BackendHandle, String> {
518 let backend = if spec.is_null() {
519 default_backend().clone()
520 } else {
521 let raw = unsafe { CStr::from_ptr(spec) };
522 let raw = raw
523 .to_str()
524 .map_err(|_| "backend spec must be valid UTF-8".to_string())?;
525 Backend::parse(raw)?
526 };
527 Ok(BackendHandle {
528 spec: cstring_lossy(&backend.to_string()),
529 inner: backend,
530 })
531 };
532
533 let handle = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
534 Ok(Ok(handle)) => handle,
535 Ok(Err(msg)) => {
536 set_error(out_err, &msg);
537 return ptr::null_mut();
538 }
539 Err(_) => {
540 set_error(out_err, "panic while constructing backend");
541 return ptr::null_mut();
542 }
543 };
544
545 Box::into_raw(Box::new(handle)).cast::<howzat_backend_t>()
546}
547
548#[unsafe(no_mangle)]
549pub unsafe extern "C" fn howzat_backend_free(backend: *mut howzat_backend_t) {
550 if backend.is_null() {
551 return;
552 }
553 unsafe {
554 drop(Box::from_raw(backend.cast::<BackendHandle>()));
555 }
556}
557
558#[unsafe(no_mangle)]
559pub unsafe extern "C" fn howzat_backend_spec(backend: *const howzat_backend_t) -> *const c_char {
560 let Some(backend) = (unsafe { ref_backend(backend) }) else {
561 return ptr::null();
562 };
563 backend.spec.as_ptr()
564}
565
566fn solve_row_major_f64(
567 backend: &Backend,
568 data: *const f64,
569 rows: usize,
570 cols: usize,
571 repr: howzat_representation_t,
572) -> Result<BackendRunAny, String> {
573 let len = rows
574 .checked_mul(cols)
575 .ok_or_else(|| "rows * cols overflow".to_string())?;
576 if data.is_null() {
577 return Err("data must not be NULL".to_string());
578 }
579 if rows == 0 || cols == 0 {
580 return Err("rows and cols must be non-zero".to_string());
581 }
582 let slice = unsafe { std::slice::from_raw_parts(data, len) };
583 let config = BackendRunConfig {
584 output_coefficients: true,
585 ..BackendRunConfig::default()
586 };
587
588 let repr = match repr {
589 howzat_representation_t::HOWZAT_REPR_EUCLIDEAN_VERTICES => Representation::EuclideanVertices,
590 howzat_representation_t::HOWZAT_REPR_INEQUALITY => Representation::Inequality,
591 howzat_representation_t::HOWZAT_REPR_HOMOGENEOUS_GENERATORS => {
592 Representation::HomogeneousGenerators
593 }
594 };
595 if repr == Representation::Inequality && cols < 2 {
596 return Err("inequality matrix must have at least 2 columns".to_string());
597 }
598 if repr == Representation::HomogeneousGenerators && cols < 2 {
599 return Err("generator matrix must have at least 2 columns".to_string());
600 }
601 backend
602 .solve_row_major(repr, slice, rows, cols, &config)
603 .map_err(|e| e.to_string())
604}
605
606fn solve_row_major_i64(
607 backend: &Backend,
608 data: *const i64,
609 rows: usize,
610 cols: usize,
611 repr: howzat_representation_t,
612) -> Result<BackendRunAny, String> {
613 let len = rows
614 .checked_mul(cols)
615 .ok_or_else(|| "rows * cols overflow".to_string())?;
616 if data.is_null() {
617 return Err("data must not be NULL".to_string());
618 }
619 if rows == 0 || cols == 0 {
620 return Err("rows and cols must be non-zero".to_string());
621 }
622 let slice = unsafe { std::slice::from_raw_parts(data, len) };
623 let config = BackendRunConfig {
624 output_coefficients: true,
625 ..BackendRunConfig::default()
626 };
627
628 let repr = match repr {
629 howzat_representation_t::HOWZAT_REPR_EUCLIDEAN_VERTICES => Representation::EuclideanVertices,
630 howzat_representation_t::HOWZAT_REPR_INEQUALITY => Representation::Inequality,
631 howzat_representation_t::HOWZAT_REPR_HOMOGENEOUS_GENERATORS => {
632 Representation::HomogeneousGenerators
633 }
634 };
635 if repr == Representation::Inequality && cols < 2 {
636 return Err("inequality matrix must have at least 2 columns".to_string());
637 }
638 if repr == Representation::HomogeneousGenerators && cols < 2 {
639 return Err("generator matrix must have at least 2 columns".to_string());
640 }
641 backend
642 .solve_row_major_exact(repr, slice, rows, cols, &config)
643 .map_err(|e| e.to_string())
644}
645
646fn solve_row_major_gmprat(
647 backend: &Backend,
648 data: *const mpq_srcptr,
649 rows: usize,
650 cols: usize,
651 repr: howzat_representation_t,
652) -> Result<BackendRunAny, String> {
653 use gmp_mpfr_sys::gmp::mpq_t;
654 use rug::rational::BorrowRational;
655
656 let len = rows
657 .checked_mul(cols)
658 .ok_or_else(|| "rows * cols overflow".to_string())?;
659 if data.is_null() {
660 return Err("data must not be NULL".to_string());
661 }
662 if rows == 0 || cols == 0 {
663 return Err("rows and cols must be non-zero".to_string());
664 }
665 let slice = unsafe { std::slice::from_raw_parts(data, len) };
666 let mut values: Vec<calculo::num::RugRat> = Vec::with_capacity(len);
667 for &ptr in slice {
668 if ptr.is_null() {
669 return Err("gmprat data entries must not be NULL".to_string());
670 }
671 let raw = unsafe { *(ptr.cast::<mpq_t>()) };
672 let borrow = unsafe { BorrowRational::from_raw(raw) };
673 values.push(calculo::num::RugRat((&*borrow).clone()));
674 }
675
676 let config = BackendRunConfig {
677 output_coefficients: true,
678 ..BackendRunConfig::default()
679 };
680
681 let repr = match repr {
682 howzat_representation_t::HOWZAT_REPR_EUCLIDEAN_VERTICES => Representation::EuclideanVertices,
683 howzat_representation_t::HOWZAT_REPR_INEQUALITY => Representation::Inequality,
684 howzat_representation_t::HOWZAT_REPR_HOMOGENEOUS_GENERATORS => {
685 Representation::HomogeneousGenerators
686 }
687 };
688 if repr == Representation::Inequality && cols < 2 {
689 return Err("inequality matrix must have at least 2 columns".to_string());
690 }
691 if repr == Representation::HomogeneousGenerators && cols < 2 {
692 return Err("generator matrix must have at least 2 columns".to_string());
693 }
694 backend
695 .solve_row_major_exact_gmprat(repr, values, rows, cols, &config)
696 .map_err(|e| e.to_string())
697}
698
699#[unsafe(no_mangle)]
700pub unsafe extern "C" fn howzat_solve(
701 data: *const f64,
702 rows: usize,
703 cols: usize,
704 repr: howzat_representation_t,
705 out_err: *mut *mut howzat_error_t,
706) -> *mut howzat_result_t {
707 clear_error(out_err);
708
709 let make = || -> Result<ResultHandle, String> {
710 let run = solve_row_major_f64(default_backend(), data, rows, cols, repr)?;
711 build_result_any(run)
712 };
713
714 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
715 Ok(Ok(result)) => result,
716 Ok(Err(msg)) => {
717 set_error(out_err, &msg);
718 return ptr::null_mut();
719 }
720 Err(_) => {
721 set_error(out_err, "panic while solving");
722 return ptr::null_mut();
723 }
724 };
725
726 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
727}
728
729#[unsafe(no_mangle)]
730pub unsafe extern "C" fn howzat_solve_exact(
731 data: *const i64,
732 rows: usize,
733 cols: usize,
734 repr: howzat_representation_t,
735 out_err: *mut *mut howzat_error_t,
736) -> *mut howzat_result_t {
737 clear_error(out_err);
738
739 let make = || -> Result<ResultHandle, String> {
740 let run = solve_row_major_i64(default_exact_backend(), data, rows, cols, repr)?;
741 build_result_any(run)
742 };
743
744 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
745 Ok(Ok(result)) => result,
746 Ok(Err(msg)) => {
747 set_error(out_err, &msg);
748 return ptr::null_mut();
749 }
750 Err(_) => {
751 set_error(out_err, "panic while solving");
752 return ptr::null_mut();
753 }
754 };
755
756 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
757}
758
759#[unsafe(no_mangle)]
760pub unsafe extern "C" fn howzat_solve_exact_gmprat(
761 data: *const mpq_srcptr,
762 rows: usize,
763 cols: usize,
764 repr: howzat_representation_t,
765 out_err: *mut *mut howzat_error_t,
766) -> *mut howzat_result_t {
767 clear_error(out_err);
768
769 let make = || -> Result<ResultHandle, String> {
770 let run = solve_row_major_gmprat(default_exact_backend(), data, rows, cols, repr)?;
771 build_result_any(run)
772 };
773
774 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
775 Ok(Ok(result)) => result,
776 Ok(Err(msg)) => {
777 set_error(out_err, &msg);
778 return ptr::null_mut();
779 }
780 Err(_) => {
781 set_error(out_err, "panic while solving");
782 return ptr::null_mut();
783 }
784 };
785
786 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
787}
788
789#[unsafe(no_mangle)]
790pub unsafe extern "C" fn howzat_backend_solve(
791 backend: *const howzat_backend_t,
792 data: *const f64,
793 rows: usize,
794 cols: usize,
795 repr: howzat_representation_t,
796 out_err: *mut *mut howzat_error_t,
797) -> *mut howzat_result_t {
798 clear_error(out_err);
799
800 let Some(backend_ref) = unsafe { ref_backend(backend) }.map(|b| &b.inner) else {
801 set_error(out_err, "backend must not be NULL");
802 return ptr::null_mut();
803 };
804
805 let make = || -> Result<ResultHandle, String> {
806 let run = solve_row_major_f64(backend_ref, data, rows, cols, repr)?;
807 build_result_any(run)
808 };
809
810 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
811 Ok(Ok(result)) => result,
812 Ok(Err(msg)) => {
813 set_error(out_err, &msg);
814 return ptr::null_mut();
815 }
816 Err(_) => {
817 set_error(out_err, "panic while solving");
818 return ptr::null_mut();
819 }
820 };
821
822 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
823}
824
825#[unsafe(no_mangle)]
826pub unsafe extern "C" fn howzat_backend_solve_exact(
827 backend: *const howzat_backend_t,
828 data: *const i64,
829 rows: usize,
830 cols: usize,
831 repr: howzat_representation_t,
832 out_err: *mut *mut howzat_error_t,
833) -> *mut howzat_result_t {
834 clear_error(out_err);
835
836 let Some(backend_ref) = unsafe { ref_backend(backend) }.map(|b| &b.inner) else {
837 set_error(out_err, "backend must not be NULL");
838 return ptr::null_mut();
839 };
840
841 let make = || -> Result<ResultHandle, String> {
842 let run = solve_row_major_i64(backend_ref, data, rows, cols, repr)?;
843 build_result_any(run)
844 };
845
846 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
847 Ok(Ok(result)) => result,
848 Ok(Err(msg)) => {
849 set_error(out_err, &msg);
850 return ptr::null_mut();
851 }
852 Err(_) => {
853 set_error(out_err, "panic while solving");
854 return ptr::null_mut();
855 }
856 };
857
858 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
859}
860
861#[unsafe(no_mangle)]
862pub unsafe extern "C" fn howzat_backend_solve_exact_gmprat(
863 backend: *const howzat_backend_t,
864 data: *const mpq_srcptr,
865 rows: usize,
866 cols: usize,
867 repr: howzat_representation_t,
868 out_err: *mut *mut howzat_error_t,
869) -> *mut howzat_result_t {
870 clear_error(out_err);
871
872 let Some(backend_ref) = unsafe { ref_backend(backend) }.map(|b| &b.inner) else {
873 set_error(out_err, "backend must not be NULL");
874 return ptr::null_mut();
875 };
876
877 let make = || -> Result<ResultHandle, String> {
878 let run = solve_row_major_gmprat(backend_ref, data, rows, cols, repr)?;
879 build_result_any(run)
880 };
881
882 let result = match std::panic::catch_unwind(AssertUnwindSafe(make)) {
883 Ok(Ok(result)) => result,
884 Ok(Err(msg)) => {
885 set_error(out_err, &msg);
886 return ptr::null_mut();
887 }
888 Err(_) => {
889 set_error(out_err, "panic while solving");
890 return ptr::null_mut();
891 }
892 };
893
894 Box::into_raw(Box::new(result)).cast::<howzat_result_t>()
895}
896
897#[unsafe(no_mangle)]
898pub unsafe extern "C" fn howzat_result_free(result: *mut howzat_result_t) {
899 if result.is_null() {
900 return;
901 }
902 unsafe {
903 drop(Box::from_raw(result.cast::<ResultHandle>()));
904 }
905}
906
907#[unsafe(no_mangle)]
908pub unsafe extern "C" fn howzat_result_spec(result: *const howzat_result_t) -> *const c_char {
909 let Some(result) = (unsafe { ref_result(result) }) else {
910 return ptr::null();
911 };
912 result.spec.as_ptr()
913}
914
915#[unsafe(no_mangle)]
916pub unsafe extern "C" fn howzat_result_dimension(result: *const howzat_result_t) -> usize {
917 unsafe { ref_result(result) }
918 .map(|r| r.stats.dimension)
919 .unwrap_or(0)
920}
921
922#[unsafe(no_mangle)]
923pub unsafe extern "C" fn howzat_result_vertices(result: *const howzat_result_t) -> usize {
924 unsafe { ref_result(result) }
925 .map(|r| r.stats.vertices)
926 .unwrap_or(0)
927}
928
929#[unsafe(no_mangle)]
930pub unsafe extern "C" fn howzat_result_facets(result: *const howzat_result_t) -> usize {
931 unsafe { ref_result(result) }
932 .map(|r| r.stats.facets)
933 .unwrap_or(0)
934}
935
936#[unsafe(no_mangle)]
937pub unsafe extern "C" fn howzat_result_ridges(result: *const howzat_result_t) -> usize {
938 unsafe { ref_result(result) }
939 .map(|r| r.stats.ridges)
940 .unwrap_or(0)
941}
942
943#[unsafe(no_mangle)]
944pub unsafe extern "C" fn howzat_result_total_seconds(result: *const howzat_result_t) -> f64 {
945 unsafe { ref_result(result) }
946 .map(|r| r.total_seconds)
947 .unwrap_or(0.0)
948}
949
950#[unsafe(no_mangle)]
951pub unsafe extern "C" fn howzat_result_fails(result: *const howzat_result_t) -> usize {
952 unsafe { ref_result(result) }.map(|r| r.fails).unwrap_or(0)
953}
954
955#[unsafe(no_mangle)]
956pub unsafe extern "C" fn howzat_result_fallbacks(result: *const howzat_result_t) -> usize {
957 unsafe { ref_result(result) }
958 .map(|r| r.fallbacks)
959 .unwrap_or(0)
960}
961
962#[unsafe(no_mangle)]
963pub unsafe extern "C" fn howzat_result_coeff_kind(
964 result: *const howzat_result_t,
965) -> howzat_coeff_kind_t {
966 let Some(result) = (unsafe { ref_result(result) }) else {
967 return howzat_coeff_kind_t::HOWZAT_COEFF_F64;
968 };
969 match result.coefficients {
970 CoefficientsHandle::F64 { .. } => howzat_coeff_kind_t::HOWZAT_COEFF_F64,
971 CoefficientsHandle::RugRat { .. } => howzat_coeff_kind_t::HOWZAT_COEFF_GMPRAT,
972 }
973}
974
975#[unsafe(no_mangle)]
976pub unsafe extern "C" fn howzat_result_generators_shape(
977 result: *const howzat_result_t,
978 out_rows: *mut usize,
979 out_cols: *mut usize,
980) {
981 let Some(result) = (unsafe { ref_result(result) }) else {
982 return;
983 };
984 let (rows, cols) = match &result.coefficients {
985 CoefficientsHandle::F64 { generators, .. } => (generators.rows, generators.cols),
986 CoefficientsHandle::RugRat { generators, .. } => (generators.rows, generators.cols),
987 };
988 if !out_rows.is_null() {
989 unsafe { *out_rows = rows };
990 }
991 if !out_cols.is_null() {
992 unsafe { *out_cols = cols };
993 }
994}
995
996#[unsafe(no_mangle)]
997pub unsafe extern "C" fn howzat_result_inequalities_shape(
998 result: *const howzat_result_t,
999 out_rows: *mut usize,
1000 out_cols: *mut usize,
1001) {
1002 let Some(result) = (unsafe { ref_result(result) }) else {
1003 return;
1004 };
1005 let (rows, cols) = match &result.coefficients {
1006 CoefficientsHandle::F64 { inequalities, .. } => (inequalities.rows, inequalities.cols),
1007 CoefficientsHandle::RugRat { inequalities, .. } => (inequalities.rows, inequalities.cols),
1008 };
1009 if !out_rows.is_null() {
1010 unsafe { *out_rows = rows };
1011 }
1012 if !out_cols.is_null() {
1013 unsafe { *out_cols = cols };
1014 }
1015}
1016
1017#[unsafe(no_mangle)]
1018pub unsafe extern "C" fn howzat_result_generators_f64(
1019 result: *const howzat_result_t,
1020) -> howzat_f64_slice_t {
1021 let Some(result) = (unsafe { ref_result(result) }) else {
1022 return howzat_f64_slice_t {
1023 ptr: ptr::null(),
1024 len: 0,
1025 };
1026 };
1027 let CoefficientsHandle::F64 { generators, .. } = &result.coefficients else {
1028 return howzat_f64_slice_t {
1029 ptr: ptr::null(),
1030 len: 0,
1031 };
1032 };
1033 howzat_f64_slice_t {
1034 ptr: generators.data.as_ptr(),
1035 len: generators.data.len(),
1036 }
1037}
1038
1039#[unsafe(no_mangle)]
1040pub unsafe extern "C" fn howzat_result_inequalities_f64(
1041 result: *const howzat_result_t,
1042) -> howzat_f64_slice_t {
1043 let Some(result) = (unsafe { ref_result(result) }) else {
1044 return howzat_f64_slice_t {
1045 ptr: ptr::null(),
1046 len: 0,
1047 };
1048 };
1049 let CoefficientsHandle::F64 { inequalities, .. } = &result.coefficients else {
1050 return howzat_f64_slice_t {
1051 ptr: ptr::null(),
1052 len: 0,
1053 };
1054 };
1055 howzat_f64_slice_t {
1056 ptr: inequalities.data.as_ptr(),
1057 len: inequalities.data.len(),
1058 }
1059}
1060
1061#[unsafe(no_mangle)]
1062pub unsafe extern "C" fn howzat_result_generators_i64(
1063 result: *const howzat_result_t,
1064) -> howzat_i64_slice_t {
1065 let Some(result) = (unsafe { ref_result(result) }) else {
1066 return howzat_i64_slice_t {
1067 ptr: ptr::null(),
1068 len: 0,
1069 };
1070 };
1071 let CoefficientsHandle::RugRat { generators, .. } = &result.coefficients else {
1072 return howzat_i64_slice_t {
1073 ptr: ptr::null(),
1074 len: 0,
1075 };
1076 };
1077 let i64_matrix = generators.i64s.get_or_init(|| {
1078 let mut data = Vec::with_capacity(generators.data.len());
1079 for value in &generators.data {
1080 if !value.0.is_integer() {
1081 return None;
1082 }
1083 let Some(value) = value.0.numer().to_i64() else {
1084 return None;
1085 };
1086 data.push(value);
1087 }
1088 Some(CoeffMatrixI64Handle {
1089 data,
1090 })
1091 });
1092 let Some(i64_matrix) = i64_matrix else {
1093 return howzat_i64_slice_t {
1094 ptr: ptr::null(),
1095 len: 0,
1096 };
1097 };
1098 howzat_i64_slice_t {
1099 ptr: i64_matrix.data.as_ptr(),
1100 len: i64_matrix.data.len(),
1101 }
1102}
1103
1104#[unsafe(no_mangle)]
1105pub unsafe extern "C" fn howzat_result_inequalities_i64(
1106 result: *const howzat_result_t,
1107) -> howzat_i64_slice_t {
1108 let Some(result) = (unsafe { ref_result(result) }) else {
1109 return howzat_i64_slice_t {
1110 ptr: ptr::null(),
1111 len: 0,
1112 };
1113 };
1114 let CoefficientsHandle::RugRat { inequalities, .. } = &result.coefficients else {
1115 return howzat_i64_slice_t {
1116 ptr: ptr::null(),
1117 len: 0,
1118 };
1119 };
1120 let i64_matrix = inequalities.i64s.get_or_init(|| {
1121 let mut data = Vec::with_capacity(inequalities.data.len());
1122 for value in &inequalities.data {
1123 if !value.0.is_integer() {
1124 return None;
1125 }
1126 let Some(value) = value.0.numer().to_i64() else {
1127 return None;
1128 };
1129 data.push(value);
1130 }
1131 Some(CoeffMatrixI64Handle {
1132 data,
1133 })
1134 });
1135 let Some(i64_matrix) = i64_matrix else {
1136 return howzat_i64_slice_t {
1137 ptr: ptr::null(),
1138 len: 0,
1139 };
1140 };
1141 howzat_i64_slice_t {
1142 ptr: i64_matrix.data.as_ptr(),
1143 len: i64_matrix.data.len(),
1144 }
1145}
1146
1147#[unsafe(no_mangle)]
1148pub unsafe extern "C" fn howzat_result_generators_gmprat(
1149 result: *const howzat_result_t,
1150) -> howzat_mpq_slice_t {
1151 let Some(result) = (unsafe { ref_result(result) }) else {
1152 return howzat_mpq_slice_t {
1153 ptr: ptr::null(),
1154 len: 0,
1155 };
1156 };
1157 let CoefficientsHandle::RugRat { generators, .. } = &result.coefficients else {
1158 return howzat_mpq_slice_t {
1159 ptr: ptr::null(),
1160 len: 0,
1161 };
1162 };
1163 howzat_mpq_slice_t {
1164 ptr: generators.ptrs.as_ptr(),
1165 len: generators.ptrs.len(),
1166 }
1167}
1168
1169#[unsafe(no_mangle)]
1170pub unsafe extern "C" fn howzat_result_inequalities_gmprat(
1171 result: *const howzat_result_t,
1172) -> howzat_mpq_slice_t {
1173 let Some(result) = (unsafe { ref_result(result) }) else {
1174 return howzat_mpq_slice_t {
1175 ptr: ptr::null(),
1176 len: 0,
1177 };
1178 };
1179 let CoefficientsHandle::RugRat { inequalities, .. } = &result.coefficients else {
1180 return howzat_mpq_slice_t {
1181 ptr: ptr::null(),
1182 len: 0,
1183 };
1184 };
1185 howzat_mpq_slice_t {
1186 ptr: inequalities.ptrs.as_ptr(),
1187 len: inequalities.ptrs.len(),
1188 }
1189}
1190
1191#[unsafe(no_mangle)]
1192pub unsafe extern "C" fn howzat_result_generators_str(
1193 result: *const howzat_result_t,
1194) -> howzat_str_slice_t {
1195 let Some(result) = (unsafe { ref_result(result) }) else {
1196 return howzat_str_slice_t {
1197 ptr: ptr::null(),
1198 len: 0,
1199 };
1200 };
1201 let CoefficientsHandle::RugRat { generators, .. } = &result.coefficients else {
1202 return howzat_str_slice_t {
1203 ptr: ptr::null(),
1204 len: 0,
1205 };
1206 };
1207 let str_matrix = generators.strings.get_or_init(|| {
1208 let data: Vec<CString> = generators
1209 .data
1210 .iter()
1211 .map(|v| cstring_lossy(&v.to_string()))
1212 .collect();
1213 let ptrs: Vec<*const c_char> = data.iter().map(|s| s.as_ptr()).collect();
1214 CoeffMatrixStrHandle {
1215 data,
1216 ptrs,
1217 }
1218 });
1219 howzat_str_slice_t {
1220 ptr: str_matrix.ptrs.as_ptr(),
1221 len: str_matrix.ptrs.len(),
1222 }
1223}
1224
1225#[unsafe(no_mangle)]
1226pub unsafe extern "C" fn howzat_result_inequalities_str(
1227 result: *const howzat_result_t,
1228) -> howzat_str_slice_t {
1229 let Some(result) = (unsafe { ref_result(result) }) else {
1230 return howzat_str_slice_t {
1231 ptr: ptr::null(),
1232 len: 0,
1233 };
1234 };
1235 let CoefficientsHandle::RugRat { inequalities, .. } = &result.coefficients else {
1236 return howzat_str_slice_t {
1237 ptr: ptr::null(),
1238 len: 0,
1239 };
1240 };
1241 let str_matrix = inequalities.strings.get_or_init(|| {
1242 let data: Vec<CString> = inequalities
1243 .data
1244 .iter()
1245 .map(|v| cstring_lossy(&v.to_string()))
1246 .collect();
1247 let ptrs: Vec<*const c_char> = data.iter().map(|s| s.as_ptr()).collect();
1248 CoeffMatrixStrHandle {
1249 data,
1250 ptrs,
1251 }
1252 });
1253 howzat_str_slice_t {
1254 ptr: str_matrix.ptrs.as_ptr(),
1255 len: str_matrix.ptrs.len(),
1256 }
1257}
1258
1259#[unsafe(no_mangle)]
1260pub unsafe extern "C" fn howzat_result_vertex_positions(
1261 result: *const howzat_result_t,
1262) -> howzat_f64_slice_t {
1263 let Some(result) = (unsafe { ref_result(result) }) else {
1264 return howzat_f64_slice_t {
1265 ptr: ptr::null(),
1266 len: 0,
1267 };
1268 };
1269 let Some(buf) = result.vertex_positions.as_ref() else {
1270 return howzat_f64_slice_t {
1271 ptr: ptr::null(),
1272 len: 0,
1273 };
1274 };
1275 howzat_f64_slice_t {
1276 ptr: buf.as_ptr(),
1277 len: buf.len(),
1278 }
1279}
1280
1281#[unsafe(no_mangle)]
1282pub unsafe extern "C" fn howzat_result_vertex_positions_shape(
1283 result: *const howzat_result_t,
1284 out_vertex_count: *mut usize,
1285 out_dim: *mut usize,
1286) {
1287 if let Some(result) = unsafe { ref_result(result) } {
1288 if !out_vertex_count.is_null() {
1289 unsafe {
1290 *out_vertex_count = result.vertex_positions_count;
1291 }
1292 }
1293 if !out_dim.is_null() {
1294 unsafe {
1295 *out_dim = result.vertex_positions_dim;
1296 }
1297 }
1298 }
1299}
1300
1301#[unsafe(no_mangle)]
1302pub unsafe extern "C" fn howzat_result_facets_to_vertices_offsets(
1303 result: *const howzat_result_t,
1304) -> howzat_usize_slice_t {
1305 let Some(result) = (unsafe { ref_result(result) }) else {
1306 return howzat_usize_slice_t {
1307 ptr: ptr::null(),
1308 len: 0,
1309 };
1310 };
1311 howzat_usize_slice_t {
1312 ptr: result.facets_to_vertices_offsets.as_ptr(),
1313 len: result.facets_to_vertices_offsets.len(),
1314 }
1315}
1316
1317#[unsafe(no_mangle)]
1318pub unsafe extern "C" fn howzat_result_facets_to_vertices_data(
1319 result: *const howzat_result_t,
1320) -> howzat_usize_slice_t {
1321 let Some(result) = (unsafe { ref_result(result) }) else {
1322 return howzat_usize_slice_t {
1323 ptr: ptr::null(),
1324 len: 0,
1325 };
1326 };
1327 howzat_usize_slice_t {
1328 ptr: result.facets_to_vertices_data.as_ptr(),
1329 len: result.facets_to_vertices_data.len(),
1330 }
1331}
1332
1333#[unsafe(no_mangle)]
1334pub unsafe extern "C" fn howzat_result_vertex_adjacency_kind(
1335 result: *const howzat_result_t,
1336) -> howzat_graph_kind_t {
1337 let Some(result) = (unsafe { ref_result(result) }) else {
1338 return howzat_graph_kind_t::HOWZAT_GRAPH_SPARSE;
1339 };
1340 match &result.vertex_adjacency {
1341 GraphHandle::Dense(_) => howzat_graph_kind_t::HOWZAT_GRAPH_DENSE,
1342 GraphHandle::Sparse(_) => howzat_graph_kind_t::HOWZAT_GRAPH_SPARSE,
1343 }
1344}
1345
1346#[unsafe(no_mangle)]
1347pub unsafe extern "C" fn howzat_result_facet_adjacency_kind(
1348 result: *const howzat_result_t,
1349) -> howzat_graph_kind_t {
1350 let Some(result) = (unsafe { ref_result(result) }) else {
1351 return howzat_graph_kind_t::HOWZAT_GRAPH_SPARSE;
1352 };
1353 match &result.facet_adjacency {
1354 GraphHandle::Dense(_) => howzat_graph_kind_t::HOWZAT_GRAPH_DENSE,
1355 GraphHandle::Sparse(_) => howzat_graph_kind_t::HOWZAT_GRAPH_SPARSE,
1356 }
1357}
1358
1359#[unsafe(no_mangle)]
1360pub unsafe extern "C" fn howzat_result_vertex_adjacency_dense(
1361 result: *const howzat_result_t,
1362) -> *const howzat_dense_graph_t {
1363 let Some(result) = (unsafe { ref_result(result) }) else {
1364 return ptr::null();
1365 };
1366 match &result.vertex_adjacency {
1367 GraphHandle::Dense(g) => g as *const DenseGraphHandle as *const howzat_dense_graph_t,
1368 GraphHandle::Sparse(_) => ptr::null(),
1369 }
1370}
1371
1372#[unsafe(no_mangle)]
1373pub unsafe extern "C" fn howzat_result_vertex_adjacency_sparse(
1374 result: *const howzat_result_t,
1375) -> *const howzat_adjacency_list_t {
1376 let Some(result) = (unsafe { ref_result(result) }) else {
1377 return ptr::null();
1378 };
1379 match &result.vertex_adjacency {
1380 GraphHandle::Sparse(g) => g as *const AdjacencyListHandle as *const howzat_adjacency_list_t,
1381 GraphHandle::Dense(_) => ptr::null(),
1382 }
1383}
1384
1385#[unsafe(no_mangle)]
1386pub unsafe extern "C" fn howzat_result_facet_adjacency_dense(
1387 result: *const howzat_result_t,
1388) -> *const howzat_dense_graph_t {
1389 let Some(result) = (unsafe { ref_result(result) }) else {
1390 return ptr::null();
1391 };
1392 match &result.facet_adjacency {
1393 GraphHandle::Dense(g) => g as *const DenseGraphHandle as *const howzat_dense_graph_t,
1394 GraphHandle::Sparse(_) => ptr::null(),
1395 }
1396}
1397
1398#[unsafe(no_mangle)]
1399pub unsafe extern "C" fn howzat_result_facet_adjacency_sparse(
1400 result: *const howzat_result_t,
1401) -> *const howzat_adjacency_list_t {
1402 let Some(result) = (unsafe { ref_result(result) }) else {
1403 return ptr::null();
1404 };
1405 match &result.facet_adjacency {
1406 GraphHandle::Sparse(g) => g as *const AdjacencyListHandle as *const howzat_adjacency_list_t,
1407 GraphHandle::Dense(_) => ptr::null(),
1408 }
1409}
1410
1411#[unsafe(no_mangle)]
1412pub unsafe extern "C" fn howzat_dense_graph_node_count(
1413 graph: *const howzat_dense_graph_t,
1414) -> usize {
1415 unsafe { ref_dense_graph(graph) }
1416 .map(|g| g.inner.family_size())
1417 .unwrap_or(0)
1418}
1419
1420#[unsafe(no_mangle)]
1421pub unsafe extern "C" fn howzat_dense_graph_degree(
1422 graph: *const howzat_dense_graph_t,
1423 node: usize,
1424) -> usize {
1425 let Some(graph) = (unsafe { ref_dense_graph(graph) }) else {
1426 return 0;
1427 };
1428 if node >= graph.inner.family_size() {
1429 return 0;
1430 }
1431 graph.inner.sets()[node].cardinality()
1432}
1433
1434#[unsafe(no_mangle)]
1435pub unsafe extern "C" fn howzat_dense_graph_contains(
1436 graph: *const howzat_dense_graph_t,
1437 node: usize,
1438 neighbor: usize,
1439) -> bool {
1440 let Some(graph) = (unsafe { ref_dense_graph(graph) }) else {
1441 return false;
1442 };
1443 let node_count = graph.inner.family_size();
1444 if node >= node_count || neighbor >= node_count {
1445 return false;
1446 }
1447 graph.inner.sets()[node].contains(neighbor)
1448}
1449
1450#[unsafe(no_mangle)]
1451pub unsafe extern "C" fn howzat_dense_graph_neighbors(
1452 graph: *const howzat_dense_graph_t,
1453 node: usize,
1454 out: *mut usize,
1455 out_cap: usize,
1456) -> usize {
1457 let Some(graph) = (unsafe { ref_dense_graph(graph) }) else {
1458 return 0;
1459 };
1460 let node_count = graph.inner.family_size();
1461 if node >= node_count {
1462 return 0;
1463 }
1464
1465 let row = &graph.inner.sets()[node];
1466 let degree = row.cardinality();
1467 if out.is_null() || out_cap == 0 {
1468 return degree;
1469 }
1470
1471 let mut written = 0usize;
1472 for idx in row.iter().raw() {
1473 if written >= out_cap {
1474 break;
1475 }
1476 unsafe {
1477 *out.add(written) = idx;
1478 }
1479 written += 1;
1480 }
1481 degree
1482}
1483
1484#[unsafe(no_mangle)]
1485pub unsafe extern "C" fn howzat_adjacency_list_node_count(
1486 graph: *const howzat_adjacency_list_t,
1487) -> usize {
1488 unsafe { ref_sparse_graph(graph) }
1489 .map(|g| g.inner.num_vertices())
1490 .unwrap_or(0)
1491}
1492
1493#[unsafe(no_mangle)]
1494pub unsafe extern "C" fn howzat_adjacency_list_degree(
1495 graph: *const howzat_adjacency_list_t,
1496 node: usize,
1497) -> usize {
1498 let Some(graph) = (unsafe { ref_sparse_graph(graph) }) else {
1499 return 0;
1500 };
1501 if node >= graph.inner.num_vertices() {
1502 return 0;
1503 }
1504 graph.inner.degree(node)
1505}
1506
1507#[unsafe(no_mangle)]
1508pub unsafe extern "C" fn howzat_adjacency_list_contains(
1509 graph: *const howzat_adjacency_list_t,
1510 node: usize,
1511 neighbor: usize,
1512) -> bool {
1513 let Some(graph) = (unsafe { ref_sparse_graph(graph) }) else {
1514 return false;
1515 };
1516 let node_count = graph.inner.num_vertices();
1517 if node >= node_count || neighbor >= node_count {
1518 return false;
1519 }
1520 graph.inner.contains(node, neighbor)
1521}
1522
1523#[unsafe(no_mangle)]
1524pub unsafe extern "C" fn howzat_adjacency_list_neighbors(
1525 graph: *const howzat_adjacency_list_t,
1526 node: usize,
1527) -> howzat_usize_slice_t {
1528 let Some(graph) = (unsafe { ref_sparse_graph(graph) }) else {
1529 return howzat_usize_slice_t {
1530 ptr: ptr::null(),
1531 len: 0,
1532 };
1533 };
1534 if node >= graph.inner.num_vertices() {
1535 return howzat_usize_slice_t {
1536 ptr: ptr::null(),
1537 len: 0,
1538 };
1539 }
1540 let neighbors = graph.inner.neighbors(node);
1541 howzat_usize_slice_t {
1542 ptr: neighbors.as_ptr(),
1543 len: neighbors.len(),
1544 }
1545}