Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rgsl-nlinear library to rust-GSL. #159

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3f75b82
Add rgsl-nlinear library to rust-GSL.
lmandres Oct 19, 2024
237f15a
Make changes to solver_progress function.
lmandres Oct 19, 2024
ac37199
Change return of parameter variance to parameter error.
lmandres Oct 19, 2024
b5dc9a4
Correcting return of parameter errors.
lmandres Oct 19, 2024
a32fb54
Added reason for stopping as part of return tuple.
lmandres Oct 19, 2024
4994b34
Changed setting df function to be referred by slice.
lmandres Oct 19, 2024
49dc542
Add error thing for short n.
lmandres Oct 20, 2024
1d499e3
Change function to pub extern C.
lmandres Oct 26, 2024
74e9426
Remove rgsl-cblib and move code to rgsl-nlinear.
lmandres Oct 27, 2024
5d3e7cb
Remove unused import.
lmandres Oct 27, 2024
3ddfc03
Allow lint of improper_ctypes warning.
lmandres Oct 27, 2024
34ecff8
Add functions and struct to avoid Segmentation fault.
lmandres Oct 28, 2024
71f87bd
Add first steps to nlinear resolver.
lmandres Oct 29, 2024
187167f
Allow multifit nlinear to call call_df().
lmandres Oct 30, 2024
cc19558
Return parameters, parameter errors, and status in Result().
lmandres Oct 30, 2024
39129a8
Point multifit nlinear functions to gslsys-mfnlin.
lmandres Oct 30, 2024
4432fe9
Remove rgsl-nlinear crate.
lmandres Oct 30, 2024
400c436
Add example for multifit_nlinear.
lmandres Oct 30, 2024
5bbfa9e
Remove gslsys-mfnlin and move to gsl-sys crate.
lmandres Oct 31, 2024
36bcb8c
Add error if params length greater than var length.
lmandres Oct 31, 2024
9559f4f
Modify rcond to only halt loop if not first iteration.
lmandres Oct 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ edition = "2021"

[dependencies]
sys = { path = "gsl-sys", package = "GSL-sys", version = "3.0.0" }
rgsl_nlinear = { path = "rgsl-nlinear", package = "rgsl-nlinear", version = "0.1.0" }
paste = "1.0"

[features]
Expand Down
15 changes: 15 additions & 0 deletions rgsl-nlinear/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you create this new crate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if you wanted rust-GSL to be pure Rust or not. It could easily be combined with the Rust-GSL crate.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want it to remain pure Rust indeed. But then, why do you need this C code?

Copy link
Author

@lmandres lmandres Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm posting a link to an issue comment that I submitted a few weeks ago:

#68 (comment)

I had trouble setting the default parameters in Rust using just the GSL-sys library. All the function does is return a struct, but the struct, written in C, returns other substructs, some of which contain references to C functions. I think this is what is causing Bus errors and Segmentation faults when setting the default parameters.

I haven't been able to find a resolution to this, but if I can get past this, it takes me a little closer to fully implementing it in Rust.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better to fix the issue rather than trying to rewrite some code in C.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. However, I only have about a month of Rust under my belt. I just got the rgsl-nlinear crate to work how I would expect it to work, and I have the feeling that this issue is beyond my current level of expertise, but I have some ideas on a way forward.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured out a workaround to the issue that I was experiencing and have rewritten the crate in Rust. Please let me know if I can resolve this conversation.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks much better. But I'm still not sure why it's creating a new crate.

name = "rgsl-nlinear"
version = "0.1.0"
edition = "2021"

[build-dependencies]
cc = "1.0"
pkg-config = "0.3"

[dependencies]
GSL-sys = { version = "3.0.0", path = "../gsl-sys" }

[lib]
name = "rgsl_nlinear"
crate-type = ["dylib", "rlib"]
28 changes: 28 additions & 0 deletions rgsl-nlinear/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
extern crate pkg_config;


fn main() {

cc::Build::new()
.file("src/multifit_nlinear.c")
.compile("rgslmfnlin");

if std::process::Command::new("pkg-config").output().is_err() {
println!("cargo:rustc-link-lib=gsl");
println!("cargo:rustc-link-lib=gslcblas");
println!("cargo:rustc-link-lib=rgslmfnlin");
return;
}

if pkg_config::probe_library("gsl").is_err() {
println!("cargo:rustc-link-lib=gsl");
}
if pkg_config::probe_library("gslcblas").is_err() {
println!("cargo:rustc-link-lib=gslcblas");
}
if pkg_config::probe_library("rgslmfnlin").is_err() {
println!("cargo:rustc-link-lib=rgslmfnlin");
}

println!("cargo::rerun-if-changed=src/multifit_nlinear.c");
}
66 changes: 66 additions & 0 deletions rgsl-nlinear/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![crate_name = "rgsl_nlinear"]

use gsl_sys::gsl_vector;
use gsl_sys::gsl_vector_get;


pub mod multifit_nlinear;

#[no_mangle]
#[allow(improper_ctypes_definitions)]
pub extern "C" fn rust_callback_f(
func_f: fn(Vec<f64>, f64, Vec<f64>) -> f64,
params: *const gsl_vector,
params_len: usize,
t: f64,
args: *const gsl_vector,
args_len: usize
) -> f64 {

let mut params_vector = Vec::new();
let mut args_vector = Vec::new();

for i in 0..params_len {
unsafe {
params_vector.push(gsl_vector_get(params, i));
}
}

for i in 0..args_len {
unsafe {
args_vector.push(gsl_vector_get(args, i));
}
}

func_f(params_vector, t, args_vector)
}

#[no_mangle]
pub extern "C" fn rust_callback_dfs(
func_dfs: &Vec<fn(Vec<f64>, f64, Vec<f64>) -> f64>,
params: *const gsl_vector,
params_len: usize,
func_i: usize,
t: f64,
args: *const gsl_vector,
args_len: usize
) -> f64 {

let mut params_vector = Vec::new();
let mut args_vector = Vec::new();
let func_df = func_dfs[func_i];

for i in 0..params_len {
unsafe {
params_vector.push(gsl_vector_get(params, i));
}
}

for i in 0..args_len {
unsafe {
args_vector.push(gsl_vector_get(args, i));
}
}

func_df(params_vector, t, args_vector)
}
232 changes: 232 additions & 0 deletions rgsl-nlinear/src/multifit_nlinear.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_multifit_nlinear.h>

typedef double (*opt_function)(void*, double, void*);

extern double rust_callback_f(opt_function, const gsl_vector*, size_t, double, const gsl_vector*, size_t);
extern double rust_callback_dfs(opt_function*, const gsl_vector*, size_t, size_t, double, const gsl_vector*, size_t);

struct data {
opt_function func_f;
opt_function* func_dfs;
size_t params_len;
double* ts;
double* ys;
size_t vars_len;
double* args;
size_t args_len;
};

int
call_f (const gsl_vector * x, void *data,
gsl_vector * f)
{
size_t params_len = ((struct data *)data)->params_len;
double *t = ((struct data *)data)->ts;
double *y = ((struct data *)data)->ys;
size_t n = ((struct data *)data)->vars_len;

opt_function func_f = ((struct data *)data)->func_f;

double *args = ((struct data *)data)->args;
size_t args_len = ((struct data *)data)->args_len;
gsl_vector_view args_vec = gsl_vector_view_array(args, args_len);

for (size_t i = 0; i < n; i++)
{
/* Model Yi = A * exp(-lambda * t_i) + b */
double Yi = rust_callback_f(func_f, x, params_len, t[i], &args_vec.vector, args_len);
gsl_vector_set (f, i, Yi - y[i]);
}

return GSL_SUCCESS;
}

int
call_dfs (const gsl_vector * x, void *data,
gsl_matrix * jacobian)
{
size_t params_len = ((struct data *)data)->params_len;
double *t = ((struct data *)data)->ts;
size_t n = ((struct data *)data)->vars_len;

opt_function* func_dfs = ((struct data *)data)->func_dfs;

double *args = ((struct data *)data)->args;
size_t args_len = ((struct data *)data)->args_len;
gsl_vector_view args_vec = gsl_vector_view_array(args, args_len);

for (size_t i = 0; i < n; i++) {
for (size_t j = 0; j < params_len; j++) {
gsl_matrix_set (jacobian, i, j, rust_callback_dfs(func_dfs, x, params_len, j, t[i], &args_vec.vector, args_len));
}
}

return GSL_SUCCESS;
}


void
solver_progress(const size_t iter, const size_t vector_len,
const gsl_multifit_nlinear_workspace *w)
{
gsl_vector *x = w->x;
double rcond;

/* compute reciprocal condition number of J(x) */
gsl_multifit_nlinear_rcond(&rcond, w);

printf("iter %2zu: ", iter);
for (size_t i = 0; i < vector_len; i++) {
printf("par[%lu] = %.4f, ", i, gsl_vector_get(x, i));
}
printf("cond(J) = %8.4f\n", 1.0 / rcond);
}

void run_gsl_multifit_nlinear_df(
opt_function func_f,
opt_function* func_dfs,
double* params,
double* parerr,
size_t params_len,
double* ts,
double* ys,
size_t vars_len,
double* args,
size_t args_len,
size_t max_iters,
int* status
) {

const gsl_multifit_nlinear_type *T = gsl_multifit_nlinear_trust;
gsl_multifit_nlinear_workspace *w;
gsl_multifit_nlinear_fdf fdf;
gsl_multifit_nlinear_parameters fdf_params = gsl_multifit_nlinear_default_parameters();

gsl_vector *f;
gsl_matrix *jacobian;
gsl_matrix *covar = gsl_matrix_alloc(params_len, params_len);

struct data d = {
func_f,
func_dfs,
params_len,
ts,
ys,
vars_len,
args,
args_len
};

gsl_vector_view x = gsl_vector_view_array(params, params_len);
double dof = vars_len - params_len;
double chisq, chisq0, chisq_dof;

void* call_dfs_ptr = NULL;
if (func_dfs != NULL) {
call_dfs_ptr = &call_dfs;
}

const double xtol = 1e-08;
const double gtol = 1e-08;
const double ftol = 1e-08;

fdf.f = call_f;
fdf.df = call_dfs_ptr;
fdf.fvv = NULL;
fdf.n = vars_len;
fdf.p = params_len;
fdf.params = &d;

/* allocate workspace with default parameters */
w = gsl_multifit_nlinear_alloc(T, &fdf_params, vars_len, params_len);

/* initialize solver with starting point and weights */
gsl_multifit_nlinear_init (&x.vector, &fdf, w);

/* compute initial cost function */
f = gsl_multifit_nlinear_residual(w);
gsl_blas_ddot(f, f, &chisq0);

/* solve the system within a maximum of max_iters iterations */
for (size_t i = 0; i < max_iters; i++) {

double rcond;

gsl_multifit_nlinear_iterate(w);
gsl_multifit_nlinear_test(xtol, gtol, ftol, status, w);

if (*status != 0) {

if (*status == 1) {
printf("Reason for stopping: Small Step Size\n");
} else if (*status == 2) {
printf("Reason for stopping: Small Gradient\n");
}

break;
}

gsl_multifit_nlinear_rcond(&rcond, w);
if (isnan(rcond) && i != 0) {

printf("Reason for stopping: Invalid Status\n");
*status = -1;

break;
}

solver_progress(i, params_len, w);
}

/* compute covariance of best fit parameters */
jacobian = gsl_multifit_nlinear_jac(w);
gsl_multifit_nlinear_covar (jacobian, 0.0, covar);

/* compute final cost */
gsl_blas_ddot(f, f, &chisq);

for (size_t i = 0; i < params_len; i++) {
chisq_dof = GSL_MAX_DBL(1, sqrt(chisq / dof));
params[i] = gsl_vector_get(w->x, i);
parerr[i] = chisq_dof * sqrt(gsl_matrix_get(covar, i, i));
}

gsl_multifit_nlinear_free (w);
gsl_matrix_free (covar);
}

void run_gsl_multifit_nlinear(
opt_function func_f,
double* params,
double* parerr,
size_t params_len,
double* ts,
double* ys,
size_t vars_len,
double* args,
size_t args_len,
size_t max_iters,
int* status
) {
run_gsl_multifit_nlinear_df(
func_f,
NULL,
params,
parerr,
params_len,
ts,
ys,
vars_len,
args,
args_len,
max_iters,
status
);
}

Loading