cpmech / russell

Rust Scientific Libary. ODE and DAE (Runge-Kutta) solvers. Special functions (Bessel, Elliptic, Beta, Gamma, Erf). Linear algebra. Sparse solvers (MUMPS, UMFPACK). Probability distributions. Tensor calculus.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make solvers clonable for caching purposes

soudy opened this issue · comments

Hi,

First of all, thanks for creating this library! It has been a joy to use for my project.

In my project, I would like to precompute some sparse matrices and their factorization to reuse them later to solve for different bs. In turn, something I would like to do something like this at the beginning of my program:

let mut mat = self.create_sparse_diffusion_laplacian(params);

let mut solver = SolverUMFPACK::new().unwrap();
solver.factorize(&mut mat, None).unwrap();

// same to some cache
CACHE.push((solver, mat))

And then later, in some other function with access to cache, load the factorized solver:

let mut solver = CACHE.get(i).clone(); // this currently doesn't work --- SolverUMFPACK doesn't implement Clone trait

let mut b = populate_b();
let mut x = Vector::new(params.n_dev_cells);

solver.solve(&mut x, &mat, &b, false).unwrap();

However, at the moment I cannot clone the solver as the Clone trait is not implemented. I am not too familiar with Rust yet, or its FFI, but would it be possible to support this kind of behaviour? Or is there some other way to achieve this?

Thanks in advance!

Hello.

This request is indeed interesting. And I was about to take a look at it. However, we cannot clone the solver!

The solver holds a pointer to an underlying C structure. And this cannot be copied. See this line:

solver: *mut InterfaceUMFPACK,

I understand that your CACHE is a global static variable, right? So, we cannot save solver in the CACHE.

I guess the way to go is to pass a reference to solver from one function to another.

Cheers.

I see! That's too bad, thanks for your reply.

I was initially going to populate CACHE using once_cell, but of course the solvers don't implement Sync (I have multiple threads that I want to access the cache read-only). I ended up using unsafe with a static mut CACHE as I know I am only going to write to it once at the beginning of the program, and then only read during multi-threaded execution. Here I ran into the problem that I cannot clone the solver out of the CACHE, and using the reference is not an option as I have multiple threads reading from it.

Alternatively, could SparseMatrix be made clonable (I think it doesn't contain C pointers)? Then I can at least cache and re-use the sparse matrix and only re-do the factorization.

Hi, I've implemented Clone for COO, CSC, CSR and SparseMatrix: 9cdd778

I hope it helps.

Brilliant, thanks @cpmech!

It appears that it's possible to make the solvers clonable!

I've used the "trick" described here

Now, we can impl Send for Solver, e.g.:

unsafe impl Send for SolverUMFPACK {}

As an example, the (new) crate russell_ode factorizes two systems (one real and one complex) concurrently using the following code:

fn factorize_concurrently(&mut self) -> Result<(), StrError> {
    thread::scope(|scope| {
        let handle_real = scope.spawn(|| {
            self.solver_real
                .actual
                .factorize(&mut self.kk_real, self.params.newton.lin_sol_params)
                .unwrap();
        });
        let handle_comp = scope.spawn(|| {
            self.solver_comp
                .actual
                .factorize(&mut self.kk_comp, self.params.newton.lin_sol_params)
                .unwrap();
        });
        let err_real = handle_real.join();
        let err_comp = handle_comp.join();
        if err_real.is_err() && err_comp.is_err() {
            Err("real and complex factorizations failed")
        } else if err_real.is_err() {
            Err("real factorizations failed")
        } else if err_comp.is_err() {
            Err("complex factorizations failed")
        } else {
            Ok(())
        }
    })
}

In this case, it is important to use the thread::scope(|scope| {} approach.

Also, only UMFPACK works concurrently (MUMPS is not thread-safe).