#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#define NEED_sv_2pv_flags
#include "ppport.h"
#include <gsl/gsl_errno.h>
#include <gsl/gsl_odeiv2.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_version.h>
#include "container.h"
#include "common.h"
char* get_gsl_version () {
return GSL_VERSION;
}
int diff_eqs (double t, const double y[], double f[], void *params) {
dSP;
SV* eqn = ((struct params *)params)->eqn;
int num = ((struct params *)params)->num;
int count;
int i;
SV* holder;
int badfunc = 0;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVnv(t)));
for (i = 1; i <= num; i++) {
XPUSHs(sv_2mortal(newSVnv(y[i-1])));
}
PUTBACK;
count = call_sv(eqn, G_ARRAY);
if (count != num)
warn("Equation did not return the specified number of values");
SPAGAIN;
for (i = 1; i <= num; i++) {
/* Get return */
holder = POPs;
/* Test for numeric return */
if (looks_like_number(holder)) {
/* if numeric return then save and move on */
f[num-i] = SvNV(holder);
} else {
/* if non numeric return store 0.0 and set badfunc
N.B. if I was sure about my C mem management I would just clear then break */
if (badfunc == 0) /* only warn once */
warn("'ode_solver' has encountered a bad return value\n");
f[num-i] = 0.0;
badfunc = 1;
}
}
PUTBACK;
FREETMPS;
LEAVE;
if (badfunc)
return GSL_EBADFUNC;
return GSL_SUCCESS;
}
int jacobian_matrix (double t, const double y[], double *dfdy,
double dfdt[], void *params) {
dSP;
SV* jac = ((struct params *)params)->jac;
int num = ((struct params *)params)->num;
int count;
int i;
int row;
int column;
SV* avr_jacobian;
SV* avr_dfdt;
SV* avr_row;
SV* holder;
gsl_matrix_view dfdy_mat = gsl_matrix_view_array (dfdy, num, num);
gsl_matrix * m = &dfdy_mat.matrix;
int badfunc = 0;
ENTER;
SAVETMPS;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVnv(t)));
for (i = 1; i <= num; i++) {
XPUSHs(sv_2mortal(newSVnv(y[i-1])));
}
PUTBACK;
count = call_sv(jac, G_ARRAY);
if (count != 2)
warn("Jacobian code reference did not return two items (arrayrefs)");
SPAGAIN;
avr_dfdt = POPs;
avr_jacobian = POPs;
PUTBACK;
count = av_len((AV*)SvRV(avr_jacobian)) + 1;
if (count != num)
warn("Jacobian array reference does not contain the specified number of rows (expected %i, got %i)\n", num, count);
count = av_len((AV*)SvRV(avr_dfdt)) + 1;
if (count != num)
warn("dfdt array reference does not contain the specified number of values (expected %i, got %i)\n", num, count);
/* pack Jacobian values into a GSL matrix */
for (row = 0; row < num; row++) {
/* get array reference to row-1 in 0 base notation */
avr_row = av_shift((AV*)SvRV(avr_jacobian));
count = av_len((AV*)SvRV(avr_row)) + 1;
if (count != num)
warn("Jacobian array reference row %i does not contain the specified number of columns (expected %i, got %i)\n", row, num, count);
for (column = 0; column < num; column++) {
/* get value at (row-1, column-1) in 0 base notation */
holder = av_shift((AV*)SvRV(avr_row));
/* Test for numeric return */
if (looks_like_number(holder)) {
/* if numeric return then save and move on */
gsl_matrix_set (m, row, column, SvNV(holder));
} else {
/* if non numeric return store 0.0 and set badfunc
N.B. if I was sure about my C mem management I would just clear then break */
if (badfunc == 0) /* only warn once */
warn("'ode_solver' has encountered a bad return value (in Jacobian at (%i, %i))\n", row, column);
gsl_matrix_set (m, row, column, 0.0);
badfunc = 1;
}
}
}
/* pack dfdt */
for (i = 1; i <= num; i++) {
/* Get next value */
holder = av_shift((AV*)SvRV(avr_dfdt));
/* Test for numeric return */
if (looks_like_number(holder)) {
/* if numeric return then save and move on */
dfdt[num-i] = SvNV(holder);
} else {
/* if non numeric return store 0.0 and set badfunc
N.B. if I was sure about my C mem management I would just clear then break */
if (badfunc == 0) /* only warn once */
warn("'ode_solver' has encountered a bad return value (in Jacobian dfdt)\n");
dfdt[num-i] = 0.0;
badfunc = 1;
}
}
FREETMPS;
LEAVE;
if (badfunc)
return GSL_EBADFUNC;
return GSL_SUCCESS;
}
/* c_ode_solver needs stack to be clear when called,
I recommend `local @_;` before calling. */
SV* c_ode_solver
(SV* eqn, SV* jac, double t1, double t2, int steps, int step_type_num,
double h_init, const double h_max,
double epsabs, double epsrel, double a_y, double a_dydt) {
dSP;
int num;
int i;
double t = t1;
double * y;
SV* ret;
const gsl_odeiv2_step_type * step_type;
int has_jacobian = SvOK(jac);
double step_size = (t2 - t1) / steps;
/* create step_type_num, selected with $opt->{type}
then .pm converts user choice to number */
switch (step_type_num) {
case 1:
step_type = gsl_odeiv2_step_rk2;
break;
case 2:
step_type = gsl_odeiv2_step_rk4;
break;
case 3:
step_type = gsl_odeiv2_step_rkf45;
break;
case 4:
step_type = gsl_odeiv2_step_rkck;
break;
case 5:
step_type = gsl_odeiv2_step_rk8pd;
break;
case 6:
if (has_jacobian == 0)
warn ("The specified step type requires the Jacobian");
step_type = gsl_odeiv2_step_rk1imp;
break;
case 7:
if (has_jacobian == 0)
warn ("The specified step type requires the Jacobian");
step_type = gsl_odeiv2_step_rk2imp;
break;
case 8:
if (has_jacobian == 0)
warn ("The specified step type requires the Jacobian");
step_type = gsl_odeiv2_step_rk4imp;
break;
case 9:
if (has_jacobian == 0)
warn ("The specified step type requires the Jacobian");
step_type = gsl_odeiv2_step_bsimp;
break;
case 10:
step_type = gsl_odeiv2_step_msadams;
break;
case 11:
if (has_jacobian == 0)
warn ("The specified step type requires the Jacobian");
step_type = gsl_odeiv2_step_msbdf;
break;
default:
warn("Could not determine step type, using rk8pd");
step_type = gsl_odeiv2_step_rk8pd;
}
ENTER;
SAVETMPS;
PUSHMARK(SP);
num = call_sv(eqn, G_ARRAY|G_NOARGS);
Newx(y, num, double);
if(y == NULL)
croak ("Failed to allocate memory to 'y' in 'c_ode_solver'");
SPAGAIN;
for (i = 1; i <= num; i++) {
y[num-i] = POPn;
}
PUTBACK;
FREETMPS;
LEAVE;
ret = make_container(num, steps);
store_data(ret, num, t, y);
struct params myparams;
myparams.num = num;
myparams.eqn = eqn;
myparams.jac = jac;
gsl_odeiv2_system sys = {diff_eqs, NULL, num, &myparams};
if (has_jacobian != 0)
sys.jacobian = jacobian_matrix;
gsl_odeiv2_driver * d =
gsl_odeiv2_driver_alloc_standard_new (
&sys, step_type, h_init, epsabs, epsrel, a_y, a_dydt
);
if ( h_max != 0 )
gsl_odeiv2_driver_set_hmax(d, h_max);
for (i = 1; i <= steps; i++)
{
double ti = i * step_size + t1;
int status = gsl_odeiv2_driver_apply (d, &t, ti, y);
if (status != GSL_SUCCESS)
{
if (status != GSL_EBADFUNC)
warn("error, return value=%d\n", status);
break;
}
/* At this point I envision that PDL could be used to store the data
rather than creating tons of SVs. Of course the current behavior
should remain for those systems without PDL */
store_data(ret, num, t, y);
}
gsl_odeiv2_driver_free(d);
Safefree(y);
return ret;
}