Skip to main content

cddlib_rs/
lib.rs

1//! Safe, idiomatic Rust bindings on top of the raw `cddlib-sys` FFI.
2//!
3//! This crate owns the unsafe glue for working with cddlib matrices, polyhedra,
4//! and LP solutions. Callers should be able to use these types without touching
5//! raw pointers or the `cddlib-sys` bindings directly.
6
7use std::ffi::c_void;
8use std::marker::PhantomData;
9use std::rc::Rc;
10
11use hullabaloo::set_family::SetFamily as HbSetFamily;
12use hullabaloo::types::{
13    Representation as RepresentationMarker, RepresentationKind, RowSet as HbRowSet,
14};
15use thiserror::Error;
16pub use hullabaloo::types::{Generator, Inequality};
17
18#[cfg(all(
19    not(feature = "gmprational"),
20    not(feature = "gmp"),
21    not(feature = "f64")
22))]
23compile_error!("cddlib-rs requires at least one backend feature: f64, gmp, or gmprational");
24
25mod backend;
26mod raw;
27
28pub use backend::{CddNumber, DefaultNumber};
29
30#[cfg(feature = "gmp")]
31pub use backend::CddFloat;
32
33#[cfg(feature = "gmprational")]
34pub use backend::CddRational;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum CddErrorCode {
38    DimensionTooLarge,
39    ImproperInputFormat,
40    NegativeMatrixSize,
41    EmptyVRepresentation,
42    EmptyHRepresentation,
43    EmptyRepresentation,
44    InputFileNotFound,
45    OutputFileNotOpen,
46    NoLpObjective,
47    NoRealNumberSupport,
48    NotAvailForH,
49    NotAvailForV,
50    CannotHandleLinearity,
51    RowIndexOutOfRange,
52    ColIndexOutOfRange,
53    LpCycling,
54    NumericallyInconsistent,
55    NoError,
56}
57
58impl CddErrorCode {
59    pub fn from_raw(raw: u32) -> Self {
60        match raw {
61            0 => Self::DimensionTooLarge,
62            1 => Self::ImproperInputFormat,
63            2 => Self::NegativeMatrixSize,
64            3 => Self::EmptyVRepresentation,
65            4 => Self::EmptyHRepresentation,
66            5 => Self::EmptyRepresentation,
67            6 => Self::InputFileNotFound,
68            7 => Self::OutputFileNotOpen,
69            8 => Self::NoLpObjective,
70            9 => Self::NoRealNumberSupport,
71            10 => Self::NotAvailForH,
72            11 => Self::NotAvailForV,
73            12 => Self::CannotHandleLinearity,
74            13 => Self::RowIndexOutOfRange,
75            14 => Self::ColIndexOutOfRange,
76            15 => Self::LpCycling,
77            16 => Self::NumericallyInconsistent,
78            17 => Self::NoError,
79            other => panic!("unknown dd_ErrorType value {other}"),
80        }
81    }
82
83    pub fn as_raw(self) -> u32 {
84        match self {
85            Self::DimensionTooLarge => 0,
86            Self::ImproperInputFormat => 1,
87            Self::NegativeMatrixSize => 2,
88            Self::EmptyVRepresentation => 3,
89            Self::EmptyHRepresentation => 4,
90            Self::EmptyRepresentation => 5,
91            Self::InputFileNotFound => 6,
92            Self::OutputFileNotOpen => 7,
93            Self::NoLpObjective => 8,
94            Self::NoRealNumberSupport => 9,
95            Self::NotAvailForH => 10,
96            Self::NotAvailForV => 11,
97            Self::CannotHandleLinearity => 12,
98            Self::RowIndexOutOfRange => 13,
99            Self::ColIndexOutOfRange => 14,
100            Self::LpCycling => 15,
101            Self::NumericallyInconsistent => 16,
102            Self::NoError => 17,
103        }
104    }
105
106    fn cddlib_name(self) -> &'static str {
107        match self {
108            Self::DimensionTooLarge => "dd_DimensionTooLarge",
109            Self::ImproperInputFormat => "dd_ImproperInputFormat",
110            Self::NegativeMatrixSize => "dd_NegativeMatrixSize",
111            Self::EmptyVRepresentation => "dd_EmptyVrepresentation",
112            Self::EmptyHRepresentation => "dd_EmptyHrepresentation",
113            Self::EmptyRepresentation => "dd_EmptyRepresentation",
114            Self::InputFileNotFound => "dd_IFileNotFound",
115            Self::OutputFileNotOpen => "dd_OFileNotOpen",
116            Self::NoLpObjective => "dd_NoLPObjective",
117            Self::NoRealNumberSupport => "dd_NoRealNumberSupport",
118            Self::NotAvailForH => "dd_NotAvailForH",
119            Self::NotAvailForV => "dd_NotAvailForV",
120            Self::CannotHandleLinearity => "dd_CannotHandleLinearity",
121            Self::RowIndexOutOfRange => "dd_RowIndexOutOfRange",
122            Self::ColIndexOutOfRange => "dd_ColIndexOutOfRange",
123            Self::LpCycling => "dd_LPCycling",
124            Self::NumericallyInconsistent => "dd_NumericallyInconsistent",
125            Self::NoError => "dd_NoError",
126        }
127    }
128}
129
130impl std::fmt::Display for CddErrorCode {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        write!(f, "{} (code={})", self.cddlib_name(), self.as_raw())
133    }
134}
135
136#[derive(Debug, Error)]
137pub enum CddError {
138    #[error("cddlib returned {0}")]
139    Cdd(CddErrorCode),
140    #[error("cddlib returned a null pointer")]
141    NullPointer,
142    #[error("LP error")]
143    LpError,
144    #[error("LP did not have an optimal solution (status={0:?})")]
145    LpStatus(LpStatus),
146    #[error("cddlib operation returned failure")]
147    OpFailed,
148}
149
150#[derive(Debug, Error)]
151pub enum CddWrapperError {
152    #[error(transparent)]
153    Cdd(#[from] CddError),
154    #[error("invalid matrix dimensions (rows={rows}, cols={cols})")]
155    InvalidMatrix { rows: usize, cols: usize },
156}
157
158pub type CddResult<T> = std::result::Result<T, CddWrapperError>;
159
160fn representation_to_raw(repr: RepresentationKind) -> u32 {
161    match repr {
162        RepresentationKind::Inequality => 1,
163        RepresentationKind::Generator => 2,
164    }
165}
166
167fn representation_from_raw(raw: u32) -> RepresentationKind {
168    match raw {
169        1 => RepresentationKind::Inequality,
170        2 => RepresentationKind::Generator,
171        other => panic!("unknown dd_RepresentationType value {other}"),
172    }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum NumberType {
177    Real,
178    Rational,
179}
180
181impl NumberType {
182    fn to_raw(self) -> u32 {
183        match self {
184            NumberType::Real => 1,
185            NumberType::Rational => 2,
186        }
187    }
188
189    fn from_raw(raw: u32) -> Self {
190        match raw {
191            1 => NumberType::Real,
192            2 => NumberType::Rational,
193            other => panic!("unknown dd_NumberType value {other}"),
194        }
195    }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199pub enum LpObjective {
200    None,
201    Maximize,
202    Minimize,
203}
204
205impl LpObjective {
206    fn to_raw(self) -> u32 {
207        match self {
208            LpObjective::None => 0,
209            LpObjective::Maximize => 1,
210            LpObjective::Minimize => 2,
211        }
212    }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum LpStatus {
217    Undecided,
218    Optimal,
219    Inconsistent,
220    DualInconsistent,
221    StructuralInconsistent,
222    StructuralDualInconsistent,
223    Unbounded,
224    DualUnbounded,
225}
226
227impl LpStatus {
228    fn from_raw(raw: u32) -> Self {
229        match raw {
230            0 => LpStatus::Undecided,
231            1 => LpStatus::Optimal,
232            2 => LpStatus::Inconsistent,
233            3 => LpStatus::DualInconsistent,
234            4 => LpStatus::StructuralInconsistent,
235            5 => LpStatus::StructuralDualInconsistent,
236            6 => LpStatus::Unbounded,
237            7 => LpStatus::DualUnbounded,
238            other => panic!("unknown dd_LPStatusType value {other}"),
239        }
240    }
241}
242
243#[derive(Debug)]
244pub struct Matrix<N: CddNumber = DefaultNumber, R: RepresentationMarker = Inequality> {
245    raw: raw::MatrixData<N>,
246    _no_send_sync: PhantomData<Rc<()>>,
247    _repr: PhantomData<R>,
248}
249
250#[derive(Debug)]
251pub struct CanonicalForm<N: CddNumber = DefaultNumber, R: RepresentationMarker = Inequality> {
252    pub matrix: Matrix<N, R>,
253    pub implicit_linearity: Vec<usize>,
254    pub redundant_rows: Vec<usize>,
255    pub positions: Vec<isize>,
256}
257
258impl<N: CddNumber, R: RepresentationMarker> Matrix<N, R> {
259    pub fn new(rows: usize, cols: usize, num_type: NumberType) -> CddResult<Self> {
260        Ok(Matrix {
261            raw: raw::MatrixData::new(rows, cols, R::KIND, num_type)?,
262            _no_send_sync: PhantomData,
263            _repr: PhantomData,
264        })
265    }
266
267    pub fn rows(&self) -> usize {
268        self.raw.rows()
269    }
270
271    pub fn cols(&self) -> usize {
272        self.raw.cols()
273    }
274
275    pub fn representation(&self) -> RepresentationKind {
276        R::KIND
277    }
278
279    pub fn number_type(&self) -> NumberType {
280        self.raw.number_type()
281    }
282
283    pub fn as_raw(&self) -> *mut c_void {
284        self.raw.as_raw()
285    }
286
287    /// # Safety
288    ///
289    /// - `ptr` must be a non-null `dd_MatrixPtr` compatible with the backend `N`.
290    /// - The pointer must be valid for the lifetime of this wrapper and must be
291    ///   owned by this wrapper (it will be freed on drop).
292    pub unsafe fn from_raw(ptr: *mut c_void) -> Self {
293        Matrix {
294            raw: raw::MatrixData::from_raw(ptr),
295            _no_send_sync: PhantomData,
296            _repr: PhantomData,
297        }
298    }
299
300    pub fn clone_cdd(&self) -> CddResult<Self> {
301        Ok(Self {
302            raw: self.raw.clone_cdd()?,
303            _no_send_sync: PhantomData,
304            _repr: PhantomData,
305        })
306    }
307
308    pub fn set(&mut self, row: usize, col: usize, value: &N) {
309        self.raw.set_mytype(row, col, value);
310    }
311
312    pub fn get(&self, row: usize, col: usize) -> N {
313        self.raw.get_mytype(row, col)
314    }
315
316    pub fn set_real(&mut self, row: usize, col: usize, value: f64) {
317        self.raw.set_real(row, col, value);
318    }
319
320    pub fn set_int(&mut self, row: usize, col: usize, value: std::os::raw::c_long) {
321        self.raw.set_int(row, col, value);
322    }
323
324    pub fn get_real(&self, row: usize, col: usize) -> f64 {
325        self.raw.get_real(row, col)
326    }
327
328    pub fn set_objective_real(&mut self, coeffs: &[f64]) {
329        self.raw.set_objective_real(coeffs);
330    }
331
332    pub fn append_rows_in_place(&mut self, rows: &Matrix<N, R>) -> CddResult<()> {
333        self.raw.append_rows_in_place(&rows.raw)
334    }
335
336    pub fn append_rows(&self, rows: &Matrix<N, R>) -> CddResult<Self> {
337        Ok(Self {
338            raw: self.raw.append_rows(&rows.raw)?,
339            _no_send_sync: PhantomData,
340            _repr: PhantomData,
341        })
342    }
343
344    pub fn remove_row(&mut self, row: usize) -> CddResult<()> {
345        self.raw.remove_row(row)
346    }
347
348    pub fn is_row_redundant(&self, row: usize) -> CddResult<bool> {
349        self.raw.is_row_redundant(row)
350    }
351
352    pub fn redundant_rows(&self) -> CddResult<Vec<usize>> {
353        self.raw.redundant_rows()
354    }
355
356    pub fn canonicalize(&self) -> CddResult<CanonicalForm<N, R>> {
357        let (canon_raw, implicit, redundant, positions) = self.raw.canonicalize()?;
358        let canon = Matrix {
359            raw: canon_raw,
360            _no_send_sync: PhantomData,
361            _repr: PhantomData,
362        };
363        Ok(CanonicalForm {
364            matrix: canon,
365            implicit_linearity: implicit,
366            redundant_rows: redundant,
367            positions,
368        })
369    }
370}
371
372impl<N: CddNumber> Matrix<N, Generator> {
373    pub fn set_generator_type(&mut self, row: usize, is_vertex: bool) {
374        self.raw.set_generator_type(row, is_vertex);
375    }
376
377    pub fn from_vertices<const D: usize>(vertices: &[[N; D]]) -> CddResult<Self> {
378        if vertices.is_empty() {
379            return Err(CddWrapperError::InvalidMatrix {
380                rows: 0,
381                cols: D + 1,
382            });
383        }
384
385        let mut m = Self::new(vertices.len(), D + 1, N::DEFAULT_NUMBER_TYPE)?;
386        for (i, v) in vertices.iter().enumerate() {
387            m.set_generator_type(i, true);
388            for (j, coord) in v.iter().enumerate() {
389                m.set(i, j + 1, coord);
390            }
391        }
392        Ok(m)
393    }
394
395    /// Build a generator matrix from a dynamic list of vertices.
396    ///
397    /// The ambient dimension is determined from the first vertex. All vertices
398    /// must have the same length.
399    pub fn from_vertex_rows(vertices: &[Vec<N>]) -> CddResult<Self> {
400        let Some(first) = vertices.first() else {
401            return Err(CddWrapperError::InvalidMatrix { rows: 0, cols: 0 });
402        };
403
404        let dim = first.len();
405        if dim == 0 {
406            return Err(CddWrapperError::InvalidMatrix {
407                rows: vertices.len(),
408                cols: 0,
409            });
410        }
411
412        if vertices.iter().skip(1).any(|v| v.len() != dim) {
413            return Err(CddWrapperError::InvalidMatrix {
414                rows: vertices.len(),
415                cols: dim + 1,
416            });
417        }
418
419        let mut m = Self::new(vertices.len(), dim + 1, N::DEFAULT_NUMBER_TYPE)?;
420        for (row, coords) in vertices.iter().enumerate() {
421            m.set_generator_type(row, true);
422            for (col, coord) in coords.iter().enumerate() {
423                m.set(row, col + 1, coord);
424            }
425        }
426        Ok(m)
427    }
428
429    pub fn append_row(&self, coords: &[N], is_vertex: bool) -> CddResult<Self> {
430        if coords.len() + 1 != self.cols() {
431            return Err(CddWrapperError::InvalidMatrix {
432                rows: self.rows() + 1,
433                cols: coords.len() + 1,
434            });
435        }
436
437        let mut row = Self::new(1, self.cols(), self.number_type())?;
438        row.set_generator_type(0, is_vertex);
439        for (col, val) in coords.iter().enumerate() {
440            row.set(0, col + 1, val);
441        }
442
443        self.append_rows(&row)
444    }
445}
446
447#[derive(Debug)]
448pub struct SetFamily<N: CddNumber = DefaultNumber> {
449    raw: raw::SetFamilyData<N>,
450    _no_send_sync: PhantomData<Rc<()>>,
451}
452
453impl<N: CddNumber> SetFamily<N> {
454    /// # Safety
455    ///
456    /// - `ptr` must be a non-null `dd_SetFamilyPtr` compatible with the backend `N`.
457    /// - The pointer must be valid for the lifetime of this wrapper and must be
458    ///   owned by this wrapper (it will be freed on drop).
459    pub unsafe fn from_raw(ptr: *mut c_void) -> Self {
460        SetFamily {
461            raw: raw::SetFamilyData::from_raw(ptr),
462            _no_send_sync: PhantomData,
463        }
464    }
465
466    pub fn as_raw(&self) -> *mut c_void {
467        self.raw.as_raw()
468    }
469
470    pub fn len(&self) -> usize {
471        self.raw.len()
472    }
473
474    pub fn is_empty(&self) -> bool {
475        self.len() == 0
476    }
477
478    pub fn universe_size(&self) -> usize {
479        self.raw.universe_size()
480    }
481
482    pub fn to_adjacency_lists(&self) -> Vec<Vec<usize>> {
483        self.raw.to_adjacency_lists()
484    }
485}
486
487impl<N: CddNumber> From<SetFamily<N>> for HbSetFamily {
488    fn from(value: SetFamily<N>) -> Self {
489        let universe = value.universe_size();
490        let n = value.len();
491
492        let mut sets: Vec<HbRowSet> = Vec::with_capacity(n);
493        for idx in 0..n {
494            let members = value.raw.members(idx);
495            sets.push(HbRowSet::from_indices(universe, &members));
496        }
497
498        HbSetFamily::from_sets(universe, sets)
499    }
500}
501
502#[derive(Debug)]
503pub struct Polyhedron<N: CddNumber = DefaultNumber> {
504    raw: raw::PolyhedronData<N>,
505    _no_send_sync: PhantomData<Rc<()>>,
506}
507
508impl<N: CddNumber> Polyhedron<N> {
509    pub fn from_matrix<R: RepresentationMarker>(m: &Matrix<N, R>) -> CddResult<Self> {
510        Ok(Self {
511            raw: raw::PolyhedronData::from_matrix(&m.raw)?,
512            _no_send_sync: PhantomData,
513        })
514    }
515
516    pub fn from_vertices<const D: usize>(vertices: &[[N; D]]) -> CddResult<Self> {
517        let m = Matrix::<N, Generator>::from_vertices(vertices)?;
518        Self::from_matrix(&m)
519    }
520
521    pub fn from_vertex_rows(vertices: &[Vec<N>]) -> CddResult<Self> {
522        let m = Matrix::<N, Generator>::from_vertex_rows(vertices)?;
523        Self::from_matrix(&m)
524    }
525
526    pub fn facets(&self) -> CddResult<Matrix<N, Inequality>> {
527        Ok(Matrix {
528            raw: self.raw.facets()?,
529            _no_send_sync: PhantomData,
530            _repr: PhantomData,
531        })
532    }
533
534    pub fn generators(&self) -> CddResult<Matrix<N, Generator>> {
535        Ok(Matrix {
536            raw: self.raw.generators()?,
537            _no_send_sync: PhantomData,
538            _repr: PhantomData,
539        })
540    }
541
542    #[deprecated(
543        note = "Uses cddlib's dd_CopyAdjacency, which has a serious performance-limiting bug that \
544causes an approximately superexponential slowdown for \"large\" numbers of generators. Prefer \
545hullabaloo's adjacency construction APIs instead."
546    )]
547    pub fn adjacency(&self) -> CddResult<SetFamily<N>> {
548        Ok(SetFamily {
549            raw: self.raw.adjacency()?,
550            _no_send_sync: PhantomData,
551        })
552    }
553
554    pub fn input_adjacency(&self) -> CddResult<SetFamily<N>> {
555        Ok(SetFamily {
556            raw: self.raw.input_adjacency()?,
557            _no_send_sync: PhantomData,
558        })
559    }
560
561    pub fn incidence(&self) -> CddResult<SetFamily<N>> {
562        Ok(SetFamily {
563            raw: self.raw.incidence()?,
564            _no_send_sync: PhantomData,
565        })
566    }
567
568    pub fn input_incidence(&self) -> CddResult<SetFamily<N>> {
569        Ok(SetFamily {
570            raw: self.raw.input_incidence()?,
571            _no_send_sync: PhantomData,
572        })
573    }
574
575    pub fn append_input_rows<R: RepresentationMarker>(
576        &mut self,
577        rows: &Matrix<N, R>,
578    ) -> CddResult<()> {
579        self.raw.append_input_rows(&rows.raw)
580    }
581
582    pub fn width_in_direction_real(&self, direction: &[f64]) -> CddResult<f64> {
583        let mut h_min = self.facets()?;
584        let mut h_max = h_min.clone_cdd()?;
585
586        if direction.len() + 1 != h_max.cols() {
587            return Err(CddWrapperError::InvalidMatrix {
588                rows: h_max.rows(),
589                cols: h_max.cols(),
590            });
591        }
592
593        let cols = h_max.cols();
594        let mut coeffs = vec![0.0f64; cols];
595
596        for (i, &u_i) in direction.iter().enumerate() {
597            coeffs[i + 1] = u_i;
598        }
599
600        h_max.set_objective_real(&coeffs);
601        let lp_max = Lp::<N>::from_matrix(&mut h_max, LpObjective::Maximize)?;
602        let sol_max = lp_max.solve()?;
603        let max_val = sol_max.opt_value_real();
604
605        h_min.set_objective_real(&coeffs);
606        let lp_min = Lp::<N>::from_matrix(&mut h_min, LpObjective::Minimize)?;
607        let sol_min = lp_min.solve()?;
608        let min_val = sol_min.opt_value_real();
609
610        Ok(max_val - min_val)
611    }
612}
613
614#[derive(Debug)]
615pub struct Lp<N: CddNumber = DefaultNumber> {
616    raw: raw::LpData<N>,
617    _no_send_sync: PhantomData<Rc<()>>,
618}
619
620#[derive(Debug)]
621pub struct LpSolution<N: CddNumber = DefaultNumber> {
622    raw: raw::LpSolutionData<N>,
623    _no_send_sync: PhantomData<Rc<()>>,
624}
625
626impl<N: CddNumber> Lp<N> {
627    pub fn from_matrix(matrix: &mut Matrix<N>, objective: LpObjective) -> CddResult<Self> {
628        Ok(Lp {
629            raw: raw::LpData::from_matrix(&mut matrix.raw, objective)?,
630            _no_send_sync: PhantomData,
631        })
632    }
633
634    pub fn solve(&self) -> CddResult<LpSolution<N>> {
635        Ok(LpSolution {
636            raw: self.raw.solve()?,
637            _no_send_sync: PhantomData,
638        })
639    }
640}
641
642impl<N: CddNumber> LpSolution<N> {
643    pub fn opt_value_real(&self) -> f64 {
644        self.raw.opt_value_real()
645    }
646}
647
648#[cfg(feature = "f64")]
649pub type MatrixF64 = Matrix<f64>;
650#[cfg(feature = "f64")]
651pub type PolyhedronF64 = Polyhedron<f64>;
652
653#[cfg(feature = "gmp")]
654pub type MatrixGmpFloat = Matrix<CddFloat>;
655#[cfg(feature = "gmp")]
656pub type PolyhedronGmpFloat = Polyhedron<CddFloat>;
657
658#[cfg(feature = "gmprational")]
659pub type MatrixGmpRational = Matrix<CddRational>;
660#[cfg(feature = "gmprational")]
661pub type PolyhedronGmpRational = Polyhedron<CddRational>;
662
663impl<N: CddNumber, R: RepresentationMarker> Matrix<N, R> {
664    pub fn convert<M: CddNumber>(&self) -> CddResult<Matrix<M, R>> {
665        convert_matrix_via_real::<N, M, R>(self)
666    }
667}
668
669#[cfg(all(feature = "f64", feature = "gmp"))]
670impl<R: RepresentationMarker> From<Matrix<f64, R>> for Matrix<CddFloat, R> {
671    fn from(value: Matrix<f64, R>) -> Self {
672        value
673            .convert()
674            .expect("Matrix<f64> -> Matrix<CddFloat> conversion failed")
675    }
676}
677
678#[cfg(all(feature = "f64", feature = "gmprational"))]
679impl<R: RepresentationMarker> From<Matrix<f64, R>> for Matrix<CddRational, R> {
680    fn from(value: Matrix<f64, R>) -> Self {
681        value
682            .convert()
683            .expect("Matrix<f64> -> Matrix<CddRational> conversion failed")
684    }
685}
686
687#[cfg(all(feature = "f64", feature = "gmp"))]
688impl<R: RepresentationMarker> From<Matrix<CddFloat, R>> for Matrix<f64, R> {
689    fn from(value: Matrix<CddFloat, R>) -> Self {
690        value
691            .convert()
692            .expect("Matrix<CddFloat> -> Matrix<f64> conversion failed")
693    }
694}
695
696#[cfg(all(feature = "f64", feature = "gmprational"))]
697impl<R: RepresentationMarker> From<Matrix<CddRational, R>> for Matrix<f64, R> {
698    fn from(value: Matrix<CddRational, R>) -> Self {
699        value
700            .convert()
701            .expect("Matrix<CddRational> -> Matrix<f64> conversion failed")
702    }
703}
704
705#[cfg(all(feature = "gmp", feature = "gmprational"))]
706impl<R: RepresentationMarker> From<Matrix<CddFloat, R>> for Matrix<CddRational, R> {
707    fn from(value: Matrix<CddFloat, R>) -> Self {
708        value
709            .convert()
710            .expect("Matrix<CddFloat> -> Matrix<CddRational> conversion failed")
711    }
712}
713
714#[cfg(all(feature = "gmp", feature = "gmprational"))]
715impl<R: RepresentationMarker> From<Matrix<CddRational, R>> for Matrix<CddFloat, R> {
716    fn from(value: Matrix<CddRational, R>) -> Self {
717        value
718            .convert()
719            .expect("Matrix<CddRational> -> Matrix<CddFloat> conversion failed")
720    }
721}
722
723fn convert_matrix_via_real<Src: CddNumber, Dst: CddNumber, R: RepresentationMarker>(
724    src: &Matrix<Src, R>,
725) -> CddResult<Matrix<Dst, R>> {
726    let rows = src.rows();
727    let cols = src.cols();
728    let mut out = Matrix::<Dst, R>::new(rows, cols, Dst::DEFAULT_NUMBER_TYPE)?;
729
730    for i in 0..rows {
731        for j in 0..cols {
732            out.set_real(i, j, src.get_real(i, j));
733        }
734    }
735
736    out.raw.copy_objective_from(&src.raw);
737
738    if let Some(coeffs) = src.raw.objective_row_coeffs_real() {
739        out.set_objective_real(&coeffs);
740    }
741
742    Ok(out)
743}