diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 899dbad2..8c843dd6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -105,7 +105,7 @@ jobs:
         override: true
 
     - name: Build (exclude suitesparse)
-      run: cargo build --verbose --workspace --exclude suitesparse_ldl_sys --exclude sprs_suitesparse_ldl
+      run: cargo build --verbose --workspace --exclude suitesparse_ldl_sys --exclude sprs_suitesparse_ldl --exclude sprs_suitesparse_camd --exclude suitesparse_camd_sys
       if: matrix.os != 'ubuntu-18.04'
 
     - name: Build (all)
@@ -113,7 +113,7 @@ jobs:
       if: matrix.os == 'ubuntu-18.04'
 
     - name: Test (exclude suitesparse)
-      run: cargo test --verbose --workspace --exclude suitesparse_ldl_sys --exclude sprs_suitesparse_ldl
+      run: cargo test --verbose --workspace --exclude suitesparse_ldl_sys --exclude sprs_suitesparse_ldl --exclude sprs_suitesparse_camd --exclude suitesparse_camd_sys
       if: matrix.os != 'ubuntu-18.04'
 
     - name: Test (all)
@@ -121,7 +121,7 @@ jobs:
       if: matrix.os == 'ubuntu-18.04'
 
     - name: Test (suitesparse integration)
-      run: cargo test -p sprs-ldl --features sprs_suitesparse_ldl -Zpackage-features
+      run: cargo test -p sprs-ldl --features "sprs_suitesparse_ldl sprs_suitesparse_camd" -Zpackage-features
       if: matrix.os == 'ubuntu-18.04' && matrix.build == 'nightly'
 
   benches:
diff --git a/.travis.yml b/.travis.yml
index 75538da0..7d9549b4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,7 +19,6 @@ script:
     - cargo test --release --all --verbose
     - cargo run --example heat
     - ./.travis_rustfmt
-    - ./.travis_nightly
 
 notifications:
   email:
diff --git a/.travis_nightly b/.travis_nightly
deleted file mode 100755
index 268de7ce..00000000
--- a/.travis_nightly
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-set -ev
-
-if [ -z ${WITH_NIGHTLY+x} ]; then
-    echo "Not on nightly channel: skipping nightly only parts."
-else
-    cd sprs-benches
-    cargo build --features nightly
-    cd ..
-    cd sprs-ldl
-    cargo test --features sprs_suitesparse_ldl
-fi
diff --git a/Cargo.toml b/Cargo.toml
index 5f4f443f..7134a1ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 
 name = "sprs"
 description = "A sparse matrix library"
-version = "0.8.1"
+version = "0.9.0"
 authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
 edition = "2018"
 
@@ -51,6 +51,8 @@ members = [
     "sprs-ldl",
     "suitesparse_bindings/suitesparse_ldl_sys",
     "suitesparse_bindings/sprs_suitesparse_ldl",
+    "suitesparse_bindings/suitesparse_camd_sys",
+    "suitesparse_bindings/sprs_suitesparse_camd",
     "sprs-rand",
     "sprs-benches",
 ]
diff --git a/changelog.rst b/changelog.rst
index a9bfe01d..8f520fb2 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -2,6 +2,10 @@
 Changelog
 =========
 
+- 0.9.0
+    - Make FillInReduction enum non exhaustive to prevent excessive breakage
+      when new algorithms are implemented. **breaking change**
+    - Make rayon optional **breaking change**
 - 0.8.1
     - Expose the ``num_kinds`` module to allow generic usage of matrix market
       serialization functions in client crates
diff --git a/sprs-benches/Cargo.toml b/sprs-benches/Cargo.toml
index 24d59c58..8dd8bf73 100644
--- a/sprs-benches/Cargo.toml
+++ b/sprs-benches/Cargo.toml
@@ -5,11 +5,11 @@ authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
 edition = "2018"
 
 [dependencies.sprs]
-version = "0.8.0"
+version = "0.9.0"
 path = ".."
 
 [dependencies.sprs-rand]
-version = "0.1.0"
+version = "0.2.0"
 path = "../sprs-rand"
 
 [dependencies.plotters]
diff --git a/sprs-ldl/Cargo.toml b/sprs-ldl/Cargo.toml
index a8a1da04..2f3667c9 100644
--- a/sprs-ldl/Cargo.toml
+++ b/sprs-ldl/Cargo.toml
@@ -2,7 +2,7 @@
 
 name = "sprs-ldl"
 description = "Sparse cholesky factorization"
-version = "0.6.1"
+version = "0.7.0"
 authors = ["Vincent Barrielle"]
 edition = "2018"
 
@@ -18,10 +18,18 @@ num-traits = "0.1.32"
 
 
 [dependencies.sprs]
-version = "0.8.0"
+version = "0.9.0"
 path = ".."
 
 [dependencies.sprs_suitesparse_ldl]
-version = "0.4.0"
+version = "0.5.0"
 path = "../suitesparse_bindings/sprs_suitesparse_ldl"
 optional = true
+
+[dependencies.sprs_suitesparse_camd]
+version = "0.1.0"
+path = "../suitesparse_bindings/sprs_suitesparse_camd"
+optional = true
+
+[dev-dependencies]
+ndarray = ">=0.11.0,<0.14"
diff --git a/sprs-ldl/src/lib.rs b/sprs-ldl/src/lib.rs
index fa6f709a..5db51096 100644
--- a/sprs-ldl/src/lib.rs
+++ b/sprs-ldl/src/lib.rs
@@ -138,12 +138,22 @@ impl Ldl {
     pub fn perm<N, I>(&self, mat: CsMatViewI<N, I>) -> PermOwnedI<I>
     where
         I: SpIndex,
-        N: Copy + PartialEq,
     {
         match self.fill_red_method {
             FillInReduction::NoReduction => PermOwnedI::identity(mat.rows()),
             FillInReduction::ReverseCuthillMcKee => {
-                sprs::linalg::reverse_cuthill_mckee(mat).perm
+                sprs::linalg::reverse_cuthill_mckee(mat.structure_view()).perm
+            }
+            FillInReduction::CAMDSuiteSparse => {
+                #[cfg(not(feature = "sprs_suitesparse_camd"))]
+                panic!("Unavailable without the `sprs_suitesparse_camd` feature");
+                #[cfg(feature = "sprs_suitesparse_camd")]
+                sprs_suitesparse_camd::camd(mat.structure_view())
+            }
+            _ => {
+                unreachable!(
+                    "Unhandled method, report a bug at https://github.com/vbarrielle/sprs/issues/199"
+                )
             }
         }
     }
@@ -861,4 +871,48 @@ mod test {
         let x = ldlt.solve(&b);
         assert_eq!(x, x0);
     }
+
+    #[cfg(feature = "sprs_suitesparse_camd")]
+    #[test]
+    fn camd_ldl_solve() {
+        // 0 - A - 2 - 3
+        // | \ | \ | / |
+        // 7 - 5 - 6 - 4
+        // | / | / | \ |
+        // 8 - 9 - 1 - E
+        #[rustfmt::skip]
+        let triangles = ndarray::arr2(
+            &[[0, 7, 5],
+              [0, 5, 10],
+              [10, 5, 6],
+              [10, 6, 2],
+              [2, 6, 3],
+              [3, 6, 4],
+              [7, 8, 5],
+              [5, 8, 9],
+              [5, 9, 6],
+              [6, 9, 1],
+              [6, 1, 11],
+              [6, 11, 4]],
+        );
+        let lap_mat =
+            sprs::special_mats::tri_mesh_graph_laplacian(12, triangles.view());
+        let ldlt_camd = super::Ldl::new()
+            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
+            .fill_in_reduction(super::FillInReduction::CAMDSuiteSparse)
+            .numeric(lap_mat.view())
+            .unwrap();
+        let ldlt_cuthill = super::Ldl::new()
+            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
+            .fill_in_reduction(super::FillInReduction::ReverseCuthillMcKee)
+            .numeric(lap_mat.view())
+            .unwrap();
+        let ldlt_raw = super::Ldl::new()
+            .check_symmetry(super::SymmetryCheck::DontCheckSymmetry)
+            .fill_in_reduction(super::FillInReduction::NoReduction)
+            .numeric(lap_mat.view())
+            .unwrap();
+        assert!(ldlt_camd.nnz() < ldlt_raw.nnz());
+        assert!(ldlt_camd.nnz() < ldlt_cuthill.nnz());
+    }
 }
diff --git a/sprs-rand/Cargo.toml b/sprs-rand/Cargo.toml
index 21c5e815..bdc8916e 100644
--- a/sprs-rand/Cargo.toml
+++ b/sprs-rand/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "sprs-rand"
 description = "Random sparse matrix generation"
-version = "0.1.0"
+version = "0.2.0"
 authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
 license = "MIT OR Apache-2.0"
 edition = "2018"
@@ -13,5 +13,5 @@ rand_distr = "0.2.2"
 rand_pcg = "0.2.1"
 
 [dependencies.sprs]
-version = "0.8.0"
+version = "0.9.0"
 path = ".."
diff --git a/src/lib.rs b/src/lib.rs
index e62526cf..e567b13b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -93,9 +93,10 @@ pub use crate::sparse::{
     csmat::CsIter, csmat::OuterIterator, csmat::OuterIteratorMut,
     csmat::OuterIteratorPerm, kronecker::kronecker_product, CsMat, CsMatBase,
     CsMatI, CsMatVecView, CsMatView, CsMatViewI, CsMatViewMut, CsMatViewMutI,
-    CsVec, CsVecBase, CsVecI, CsVecView, CsVecViewI, CsVecViewMut,
-    CsVecViewMutI, SparseMat, TriMat, TriMatBase, TriMatI, TriMatIter,
-    TriMatView, TriMatViewI, TriMatViewMut, TriMatViewMutI,
+    CsStructure, CsStructureI, CsStructureView, CsStructureViewI, CsVec,
+    CsVecBase, CsVecI, CsVecView, CsVecViewI, CsVecViewMut, CsVecViewMutI,
+    SparseMat, TriMat, TriMatBase, TriMatI, TriMatIter, TriMatView,
+    TriMatViewI, TriMatViewMut, TriMatViewMutI,
 };
 
 pub use crate::sparse::symmetric::is_symmetric;
@@ -152,9 +153,11 @@ pub use PermutationCheck::*;
 
 /// The different kinds of fill-in-reduction algorithms supported by sprs
 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[non_exhaustive]
 pub enum FillInReduction {
     NoReduction,
     ReverseCuthillMcKee,
+    CAMDSuiteSparse,
 }
 
 #[cfg(feature = "approx")]
diff --git a/src/sparse.rs b/src/sparse.rs
index e175b9a2..7b4ddf7f 100644
--- a/src/sparse.rs
+++ b/src/sparse.rs
@@ -111,6 +111,11 @@ pub type CsMatViewMut<'a, N> = CsMatViewMutI<'a, N, usize>;
 // FIXME: a fixed size array would be better, but no Deref impl
 pub type CsMatVecView<'a, N> = CsMatVecView_<'a, N, usize>;
 
+pub type CsStructureViewI<'a, I, Iptr = I> = CsMatViewI<'a, (), I, Iptr>;
+pub type CsStructureView<'a> = CsStructureViewI<'a, usize>;
+pub type CsStructureI<I, Iptr = I> = CsMatI<(), I, Iptr>;
+pub type CsStructure = CsStructureI<usize>;
+
 /// A sparse vector, storing the indices of its non-zero data.
 ///
 /// A `CsVec` represents a sparse vector by storing a sorted `indices()` array
@@ -235,10 +240,11 @@ pub struct TriMatIter<RI, CI, DI> {
 mod prelude {
     pub use super::{
         CsMat, CsMatBase, CsMatI, CsMatVecView, CsMatVecView_, CsMatView,
-        CsMatViewI, CsMatViewMut, CsMatViewMutI, CsVec, CsVecBase, CsVecI,
-        CsVecView, CsVecViewI, CsVecViewMut, CsVecViewMutI, SparseMat, TriMat,
-        TriMatBase, TriMatI, TriMatIter, TriMatView, TriMatViewI,
-        TriMatViewMut, TriMatViewMutI,
+        CsMatViewI, CsMatViewMut, CsMatViewMutI, CsStructure, CsStructureI,
+        CsStructureView, CsStructureViewI, CsVec, CsVecBase, CsVecI, CsVecView,
+        CsVecViewI, CsVecViewMut, CsVecViewMutI, SparseMat, TriMat, TriMatBase,
+        TriMatI, TriMatIter, TriMatView, TriMatViewI, TriMatViewMut,
+        TriMatViewMutI,
     };
 }
 
diff --git a/src/sparse/csmat.rs b/src/sparse/csmat.rs
index dad2993b..b268d220 100644
--- a/src/sparse/csmat.rs
+++ b/src/sparse/csmat.rs
@@ -1127,6 +1127,28 @@ where
         }
     }
 
+    pub fn structure_view(&self) -> CsStructureViewI<I, Iptr> {
+        // Safety: std::slice::from_raw_parts requires its passed
+        // pointer to be valid for the whole length of the slice. We have a
+        // zero-sized type, so the length is zero, and since we cast
+        // a non-null pointer, the pointer is valid as all pointers to zero-sized
+        // types are valid if they are not null.
+        let zst_data = unsafe {
+            std::slice::from_raw_parts(
+                self.data.as_ptr() as *const (),
+                self.data.len(),
+            )
+        };
+        CsStructureViewI {
+            storage: self.storage,
+            nrows: self.nrows,
+            ncols: self.ncols,
+            indptr: &self.indptr[..],
+            indices: &self.indices[..],
+            data: zst_data,
+        }
+    }
+
     pub fn to_dense(&self) -> Array<N, Ix2>
     where
         N: Clone + Zero,
diff --git a/suitesparse_bindings/sprs_suitesparse_camd/.gitignore b/suitesparse_bindings/sprs_suitesparse_camd/.gitignore
new file mode 100644
index 00000000..2f7896d1
--- /dev/null
+++ b/suitesparse_bindings/sprs_suitesparse_camd/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/suitesparse_bindings/sprs_suitesparse_camd/Cargo.toml b/suitesparse_bindings/sprs_suitesparse_camd/Cargo.toml
new file mode 100644
index 00000000..9d59ddc2
--- /dev/null
+++ b/suitesparse_bindings/sprs_suitesparse_camd/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "sprs_suitesparse_camd"
+description = "sprs bindings to the suitesparse camd fill-in reducting ordering"
+version = "0.1.0"
+authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
+edition = "2018"
+keywords = ["sparse", "matrix", "fill-in", "permutation", "suitesparse"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies.sprs]
+version = "0.9.0"
+path = "../.."
+
+[dependencies.suitesparse_camd_sys]
+path = "../suitesparse_camd_sys/"
+version = "0.1.0"
diff --git a/suitesparse_bindings/sprs_suitesparse_camd/src/lib.rs b/suitesparse_bindings/sprs_suitesparse_camd/src/lib.rs
new file mode 100644
index 00000000..5f5c4d84
--- /dev/null
+++ b/suitesparse_bindings/sprs_suitesparse_camd/src/lib.rs
@@ -0,0 +1,108 @@
+use sprs::errors::SprsError;
+use sprs::{CsStructureI, CsStructureViewI, PermOwnedI, SpIndex};
+use suitesparse_camd_sys::*;
+
+/// Find a permutation matrix P which reduces the fill-in of the square
+/// sparse matrix `mat` in Cholesky factorization (ie, the number of nonzeros
+/// of the Cholesky factorization of P A P^T is less than for the Cholesky
+/// factorization of A).
+///
+/// If A is not symmetric, the ordering will be computed for A + A^T
+///
+/// # Errors
+///
+/// This function will error if the passed matrix is not square.
+pub fn try_camd<I, Iptr>(
+    mat: CsStructureViewI<I, Iptr>,
+) -> Result<PermOwnedI<I>, SprsError>
+where
+    I: SpIndex,
+    Iptr: SpIndex,
+{
+    let n = mat.rows();
+    if n != mat.cols() {
+        return Err(SprsError::IllegalArguments(
+            "Input to camd must be square",
+        ));
+    }
+    let mut control = [0.; CAMD_CONTROL];
+    let mut info = [0.; CAMD_INFO];
+    let (camd_res, perm) = if n <= SuiteSparseInt::MAX as usize {
+        let constraint: *const SuiteSparseInt = std::ptr::null();
+        let mat: CsStructureI<SuiteSparseInt, SuiteSparseInt> =
+            mat.to_other_types();
+        let mut perm: Vec<SuiteSparseInt> = vec![0; n];
+        let camd_res = unsafe {
+            camd_order(
+                n as SuiteSparseInt,
+                mat.indptr().as_ptr(),
+                mat.indices().as_ptr(),
+                perm.as_mut_ptr(),
+                control.as_mut_ptr(),
+                info.as_mut_ptr(),
+                constraint as *mut SuiteSparseInt,
+            ) as isize
+        };
+        let perm = perm.iter().map(|&i| I::from_usize(i as usize)).collect();
+        (camd_res, perm)
+    } else {
+        let constraint: *const SuiteSparseLong = std::ptr::null();
+        let mat: CsStructureI<SuiteSparseLong, SuiteSparseLong> =
+            mat.to_other_types();
+        let mut perm: Vec<SuiteSparseLong> = vec![0; n];
+        let camd_res = unsafe {
+            camd_l_order(
+                n as SuiteSparseLong,
+                mat.indptr().as_ptr(),
+                mat.indices().as_ptr(),
+                perm.as_mut_ptr(),
+                control.as_mut_ptr(),
+                info.as_mut_ptr(),
+                constraint as *mut SuiteSparseLong,
+            )
+        } as isize;
+        let perm = perm.iter().map(|&i| I::from_usize(i as usize)).collect();
+        (camd_res, perm)
+    };
+    // CsMat invariants guarantee sorted and non duplicate indices so this
+    // should not happen.
+    if camd_res != CAMD_OK {
+        return Err(SprsError::NonSortedIndices);
+    }
+    Ok(PermOwnedI::new(perm))
+}
+
+/// Find a permutation matrix P which reduces the fill-in of the square
+/// sparse matrix `mat` in Cholesky factorization (ie, the number of nonzeros
+/// of the Cholesky factorization of P A P^T is less than for the Cholesky
+/// factorization of A).
+///
+/// If A is not symmetric, the ordering will be computed for A + A^T
+///
+/// # Panics
+///
+/// This function will panic if the passed matrix is not square.
+pub fn camd<I, Iptr>(mat: CsStructureViewI<I, Iptr>) -> PermOwnedI<I>
+where
+    I: SpIndex,
+    Iptr: SpIndex,
+{
+    try_camd(mat).unwrap()
+}
+
+#[cfg(test)]
+mod tests {
+    use sprs::CsMatI;
+
+    #[test]
+    fn try_camd() {
+        let mat = CsMatI::new_csc(
+            (4, 4),
+            vec![0, 2, 4, 6, 8],
+            vec![0, 3, 1, 2, 1, 2, 0, 3],
+            vec![1., 2., 21., 6., 6., 2., 2., 8.],
+        );
+        let res = super::try_camd(mat.structure_view());
+        assert!(res.is_ok());
+    }
+}
diff --git a/suitesparse_bindings/sprs_suitesparse_ldl/Cargo.toml b/suitesparse_bindings/sprs_suitesparse_ldl/Cargo.toml
index 9e7aefb4..348281df 100644
--- a/suitesparse_bindings/sprs_suitesparse_ldl/Cargo.toml
+++ b/suitesparse_bindings/sprs_suitesparse_ldl/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "sprs_suitesparse_ldl"
 description = "sprs bindings to the suitesparse ldl solver"
-version = "0.4.0"
+version = "0.5.0"
 authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/vbarrielle/sprs"
@@ -17,5 +17,5 @@ path = "../suitesparse_ldl_sys/"
 version = "0.2.0"
 
 [dependencies.sprs]
-version = "0.8.0"
+version = "0.9.0"
 path = "../.."
diff --git a/suitesparse_bindings/suitesparse_camd_sys/.gitignore b/suitesparse_bindings/suitesparse_camd_sys/.gitignore
new file mode 100644
index 00000000..2f7896d1
--- /dev/null
+++ b/suitesparse_bindings/suitesparse_camd_sys/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/suitesparse_bindings/suitesparse_camd_sys/Cargo.toml b/suitesparse_bindings/suitesparse_camd_sys/Cargo.toml
new file mode 100644
index 00000000..b16a14fe
--- /dev/null
+++ b/suitesparse_bindings/suitesparse_camd_sys/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "suitesparse_camd_sys"
+version = "0.1.0"
+authors = ["Vincent Barrielle <vincent.barrielle@m4x.org>"]
+edition = "2018"
+description = "Raw bindings to SuiteSparse's CAMD algorithm"
+license = "MIT OR Apache-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = "0.2.74"
diff --git a/suitesparse_bindings/suitesparse_camd_sys/src/lib.rs b/suitesparse_bindings/suitesparse_camd_sys/src/lib.rs
new file mode 100644
index 00000000..31662b0f
--- /dev/null
+++ b/suitesparse_bindings/suitesparse_camd_sys/src/lib.rs
@@ -0,0 +1,197 @@
+#[cfg(target_os = "windows")]
+pub type SuiteSparseLong = libc::c_longlong;
+#[cfg(not(target_os = "windows"))]
+pub type SuiteSparseLong = libc::c_long;
+
+pub type SuiteSparseInt = libc::c_int;
+
+#[link(name = "camd")]
+extern "C" {
+    /// Find a permutation matrix P, represented by the permutation indices
+    /// `p`, which reduces the fill-in of the symmetric sparse matrix A
+    /// (represented by its index pointer `ap` and indices pointer `ai`)
+    /// in Cholesky factorization (ie, the number of nonzeros of the Cholesky
+    /// factorization of P A P^T is less than for the Cholesky factorization
+    /// of A).
+    ///
+    /// If A is not symmetric, the ordering will be computed for A + A^T
+    ///
+    /// # Safety and constraints
+    /// - `n` is the number of rows and columns of the matrix A and should be
+    ///   positive
+    /// - `ap` must be an array of length `n + 1`.
+    /// - `ai` must be an array of length `ap[n]`. The performance is better
+    ///   if `ai[ap[i]..ap[i+1]]` is always sorted and without duplicates.
+    /// - `p` must be an array of length `n`. Its contents can be uninitialized.
+    /// - `control` must be an array of size `CAMD_CONTROL`.
+    /// - `info` must be an array of size `CAMD_INFO`.
+    /// - `constraint` must be either the null pointer, or an array of size `n`.
+    pub fn camd_order(
+        n: SuiteSparseInt,
+        ap: *const SuiteSparseInt,
+        ai: *const SuiteSparseInt,
+        p: *mut SuiteSparseInt,
+        control: *mut libc::c_double,
+        info: *mut libc::c_double,
+        constraint: *mut SuiteSparseInt,
+    ) -> SuiteSparseInt;
+
+    /// Long version of `camd_order`, see its documentation.
+    pub fn camd_l_order(
+        n: SuiteSparseLong,
+        ap: *const SuiteSparseLong,
+        ai: *const SuiteSparseLong,
+        p: *mut SuiteSparseLong,
+        control: *mut libc::c_double,
+        info: *mut libc::c_double,
+        constraint: *mut SuiteSparseLong,
+    ) -> SuiteSparseLong;
+
+    /// Checks if the matrix A represented by its index pointer array
+    /// `ap` and its indices array `ai` is suitable to pass to `camd_order`.
+    ///
+    /// Will return `CAMD_OK` if the matrix is suitable, and `CAMD_OK_BUT_JUMBLED`
+    /// if the matrix has unsorted or duplicate row indices in one or more columns.
+    /// The matrix must be square, ie `n_rows == n_cols`.
+    ///
+    /// Otherwise `CAMD_INVALID` will be returned.
+    pub fn camd_valid(
+        n_rows: SuiteSparseInt,
+        n_cols: SuiteSparseInt,
+        ap: *const SuiteSparseInt,
+        ai: *const SuiteSparseInt,
+    ) -> SuiteSparseInt;
+
+    /// Long version of `camd_valid`, see its documentation.
+    pub fn camd_l_valid(
+        n_rows: SuiteSparseLong,
+        n_cols: SuiteSparseLong,
+        ap: *const SuiteSparseLong,
+        ai: *const SuiteSparseLong,
+    ) -> SuiteSparseLong;
+
+    /// Check if the array `constraint`, of size `n`, is valid as input
+    /// to `camd_order`. Returns `1` if valid, `0` otherwise.
+    pub fn camd_cvalid(n: SuiteSparseInt, constraint: *const SuiteSparseInt);
+
+    /// Long version of `camd_cvalid`, see its documentation.
+    pub fn camd_l_cvalid(
+        n: SuiteSparseLong,
+        constraint: *const SuiteSparseLong,
+    );
+
+    /// Fill the `control` array of size `CAMD_CONTROL` with default values
+    pub fn camd_defaults(control: *mut libc::c_double);
+
+    /// Fill the `control` array of size `CAMD_CONTROL` with default values
+    pub fn camd_l_defaults(control: *mut libc::c_double);
+
+    /// Pretty print the `control` array of size `CAMD_CONTROL`
+    pub fn camd_control(control: *const libc::c_double);
+
+    /// Pretty print the `control` array of size `CAMD_CONTROL`
+    pub fn camd_l_control(control: *const libc::c_double);
+
+    /// Pretty print the `info` array of size `CAMD_INFO`
+    pub fn camd_info(info: *const libc::c_double);
+
+    /// Pretty print the `info` array of size `CAMD_INFO`
+    pub fn camd_l_info(info: *const libc::c_double);
+}
+
+pub const CAMD_CONTROL: usize = 5;
+pub const CAMD_INFO: usize = 20;
+
+pub const CAMD_DENSE: usize = 0;
+pub const CAMD_AGGRESSIVE: usize = 1;
+
+pub const CAMD_STATUS: usize = 0;
+pub const CAMD_N: usize = 1;
+pub const CAMD_NZ: usize = 2;
+pub const CAMD_SYMMETRY: usize = 3;
+pub const CAMD_NZDIAG: usize = 4;
+pub const CAMD_NZ_A_PLUS_AT: usize = 5;
+pub const CAMD_NDENSE: usize = 6;
+pub const CAMD_MEMORY: usize = 7;
+pub const CAMD_NCMPA: usize = 8;
+pub const CAMD_LNZ: usize = 9;
+pub const CAMD_NDIV: usize = 10;
+pub const CAMD_NMULTSUBS_LDL: usize = 11;
+pub const CAMD_NMULTSUBS_LU: usize = 12;
+pub const CAMD_DMAX: usize = 13;
+
+pub const CAMD_OK: isize = 0;
+pub const CAMD_OUT_OF_MEMORY: isize = -1;
+pub const CAMD_INVALID: isize = -2;
+pub const CAMD_OK_BUT_JUMBLED: isize = 1;
+
+#[cfg(test)]
+mod tests {
+    use super::SuiteSparseInt;
+    #[test]
+    fn camd_valid() {
+        // | 0 1 3 |
+        // | 1 1 0 |
+        // | 3 0 0 |
+        let n: SuiteSparseInt = 3;
+        let ap = &[0, 2, 4, 5];
+        let ai = &[1, 2, 0, 1, 0];
+        let valid =
+            unsafe { super::camd_valid(n, n, ap.as_ptr(), ai.as_ptr()) };
+        assert_eq!(valid, super::CAMD_OK as SuiteSparseInt);
+        let ai = &[2, 1, 0, 1, 0];
+        let valid =
+            unsafe { super::camd_valid(n, n, ap.as_ptr(), ai.as_ptr()) };
+        assert_eq!(valid, super::CAMD_OK_BUT_JUMBLED as SuiteSparseInt);
+        let ai = &[1, 2, 0, 1, 1];
+        let valid =
+            unsafe { super::camd_valid(n, n, ap.as_ptr(), ai.as_ptr()) };
+        assert_eq!(valid, super::CAMD_OK as SuiteSparseInt);
+        let valid =
+            unsafe { super::camd_valid(n, n + 1, ap.as_ptr(), ai.as_ptr()) };
+        assert_eq!(valid, super::CAMD_INVALID as SuiteSparseInt);
+        let valid = unsafe {
+            super::camd_valid(n + 1, n + 1, ap.as_ptr(), ai.as_ptr())
+        };
+        assert_eq!(valid, super::CAMD_INVALID as SuiteSparseInt);
+
+        // long version test, only test once as we now have the behavior tested
+        // (we only want tot test the binding is correct now).
+        use super::SuiteSparseLong as Long;
+        let n: Long = 3;
+        let ap: &[Long] = &[0, 2, 4, 5];
+        let ai: &[Long] = &[1, 2, 0, 1, 0];
+        let valid =
+            unsafe { super::camd_l_valid(n, n, ap.as_ptr(), ai.as_ptr()) };
+        assert_eq!(valid, super::CAMD_OK as Long);
+    }
+
+    #[test]
+    fn camd_order() {
+        // | 0 1 3 |
+        // | 1 1 0 |
+        // | 3 0 0 |
+        let n: SuiteSparseInt = 3;
+        let ap = &[0, 2, 4, 5];
+        let ai = &[1, 2, 0, 1, 0];
+        let mut perm = [0; 3];
+        let mut control = [0.; super::CAMD_CONTROL];
+        let mut info = [0.; super::CAMD_INFO];
+        let constraint: *const SuiteSparseInt = std::ptr::null();
+        unsafe {
+            super::camd_defaults(control.as_mut_ptr());
+        }
+        let res = unsafe {
+            super::camd_order(
+                n,
+                ap.as_ptr(),
+                ai.as_ptr(),
+                perm.as_mut_ptr(),
+                control.as_mut_ptr(),
+                info.as_mut_ptr(),
+                constraint as *mut SuiteSparseInt,
+            )
+        };
+        assert_eq!(res, super::CAMD_OK as SuiteSparseInt);
+    }
+}