简体   繁体   中英

Passing array into function pointer (in c)?

What I'm trying to do is to solve a second order ODE using Runge Kutta. I have the outlines of the RK method, and the ODE itself built up, but I can't proceed further. I have the ODE-s in an array, and I tried passing this array to the RK, but at the f(dy[i]) parts it gives "expected double* but type double instead" error. And when I tried dividing the two ODE-s into two functions, and passing those to RK, it only worked with the first one. Passing the elements of dy instead of function pointers are not an option, as the RK itself should be able to solve an ODE containing any number of variables.

How can I properly add the array and work with it? Here is my corresponding piece of code:

double* harmOsc(double* p, double t, double* y, double* dy, int n)
{
    double k = p[0];
    double m = p[1];
    double x = y[0];
    double v = y[1];
    dy[0] = v;
    dy[1] = -k/m*x;
    return(dy);
}


double* HarmOsc1(double* dy, double t)
{
    return(dy);
}


void RK4(
    double t,       //independent variable
    double dt,      //stepsize

    double *y,      //variables
    double *dy,     //derivatives
    int n,          //number of equations
    double* (*f)(double*, double))
{

    int j;
    double* l = (double*)calloc(4*n,sizeof(double));
    for(j=0;j<n;j++)
    {
        l[0*n+j] = dt*f(dy[j],t);
        l[1*n+j] = dt*f(dy[j]+0.5*l[0*n+j],t+0.5*dt);
        l[2*n+j] = dt*f(dy[j]+0.5*l[1*n+j],t+0.5*dt);
        l[3*n+j] = dt*f(dy[j]+0.5*l[2*n+j],t+0.5*dt);
        y[j] = y[j] + (l[0*n+j] + 2*l[1*n+j] + 2*l[2*n+j] + l[3*n+j])/6;
    }
}

int main(int argc, char *argv[])
{
    double* p = (double*)calloc(2,sizeof(double));
    p[0] = 15; p[1] = 140;
    double* y = (double*)calloc(2,sizeof(double));
    y[0] = 12.4; y[1] = 1.1;
    double t=0;

    double* dy = (double*)calloc(2,sizeof(double));
    dy = harmOsc(p,t,y,dy,2);
    dy = HarmOsc1(dy,t);

    RK4(t,0.05,p,y,dy,2,&HarmOsc1);
    printf("%f, %f\n",dy[1],dy[0]);
}

The HarmOsc1 is the function I call in, so that it has the required amount of parameters.

And of course the warnings I get:

RK3.c:55:13: error: incompatible type for argument 1 of 'f'

RK3.c:55:13: note: expected 'double *' but argument is of type 'double'

RK3.c:56:6: error: incompatible type for argument 1 of 'f'

RK3.c:56:6: note: expected 'double *' but argument is of type 'double'

RK3.c:57:6: error: incompatible type for argument 1 of 'f'

RK3.c:57:6: note: expected 'double *' but argument is of type 'double'

RK3.c:58:6: error: incompatible type for argument 1 of 'f'

RK3.c:58:6: note: expected 'double *' but argument is of type 'double'

Based on clarifications provided in comments, we have established that your function RK4() is supposed to use the Runge-Kutta method to numerically solve one value of each of several ODEs of the form

y'(t) = f(y(t), t)

with initial value conditions of the form

y(t0) = y0

Where t is a scalar and each y is scalar-valued, and the f() is the same for all the ODEs (or at least is represented by the same C function).

We furthermore established that the function parameters have these meanings:

t    equivalent to t0 above
dt   the distance from t to the point at which the solutions are to be computed
y    points to an array in which to return the solutions
dy   points to the initial function values (y0 above) for all the ODEs
n    the number of ODEs to solve
f    the function `f()` above

You have a variety of problems, but I think most of them ultimately start with your declaration of RK4() 's parameter f . Note in particular that the above form for the ODEs requires f() to accept as arguments one value of the same dimension as each y 's value and another of the same dimension / number as each y 's arguments, and to return a value of the same dimension as each y 's value. But we have established that the y s are scalar functions of one scalar argument each, so f should accept two double s and return one double . That would get us to this declaration:

void RK4(
    double t,       //initial point
    double dt,      //delta
    double *y,      //result values
    double *dy,     //initial values
    int n,          //number of equations
    double (*f)(double, double)) {

You don't need f to compute results for multiple ODEs in one run because your RK4() iterates over the ODEs and calls f() separately for each one.

Next, let's look at your variable l . You are dynamically allocating enough space to store all four RK constants for each input ODE (and not freeing it), but this is useless. You use each set of RK constants only once, so you do not need to remember previous ones when you move on to the next equation. Thus, you only need space for four constants, and since that's a fixed number, you do not need dynamic allocation. You don't even really get any benefit from using an array; I would just use four scalar variables, declared inside the loop body.

    int j;
    // double* l = (double*)calloc(4*n,sizeof(double));
    for (j = 0; j < n; j++) {
        double k1 = dt * f(dy[j], t);
        double k2 = dt * f(dy[j] + 0.5 * k1, t + 0.5 * dt);
        double k3 = dt * f(dy[j] + 0.5 * k2, t + 0.5 * dt);
        double k4 = dt * f(dy[j] + k3, t + dt);  // <-- note corrections

Observe that the argument and return types with which f is now declared match the requirements of the RK equations for the form of the input ODEs.

Finally, compute the result:

        y[j] = dy[j] + (k1 + 2 * k2 + 2 * k3 + k4) / 6;

NOTE WELL the essential difference between that last line and your version -- RK computes a delta y, but your initial y values are in dy , not in y .

And that's it:

    }
}

I observe at this point that the biggest problems I had in addressing your question arose from not understanding the details of what you were trying to do. This arose from several things, large among them

  • Your variable and parameter names are short and unexpressive, and worse, to the extent that they seem to mean something, several of them actually represent something different than their names suggest

  • The code documentation is minimal and apparently incorrect. This seems to go a bit with the previous point, as the function parameter naming makes a bit more sense in light of the limited (but not entirely correct) parameter documentation.

  • The code itself was of limited help, because of the very flaws that prompted the question.

Take home: write good code documentation . Use full sentences. Describe each function's expectations for each parameter, and what it promises to do with them. Describe the return value. Document any error handling the function performs. As you do this, try to think like someone who wants to use your function, but can't consult its implementation to see what it does -- what does that person need to know? In fact, I recommend writing documentation for each function before writing that function's implementation. Do update the docs as you discover need, but the docs can help keep you on track, and writing them first can help with writing as if you can't see the implementation.

The function f is expecting a double * , ie a pointer to a double for its first parameter. However, in each place you call f , you're passing a double value, not a pointer to a double nor an array (which decays to a pointer to the first value):

    // here ------------v
    l[0*n+j] = dt*f(  dy[j],                 t);
    l[1*n+j] = dt*f(  dy[j]+0.5*l[0*n+j],    t+0.5*dt);
    l[2*n+j] = dt*f(  dy[j]+0.5*l[1*n+j],    t+0.5*dt);

If the first parameter to this function needs to be an array, then pass it an array, not a single value.

f is expecting that the first argument is double*, while you call f with dy[j], which is a double. Simply changing dy[j] to dy + j should work.

or

You might need one temp variable

double tempv;

then inside the loop,

tempv = dy[j];
...
tempv = dy[j] + ...;

call f with the address of tempv,

f(&tempv,...)

In C it is not advisable to return arrays. It is possible, but a hassle to manage what code is responsible for freeing this memory. Also, it is against some C philosophy of producing high performance code to needlessly generate repeatedly essentially the same array only to have it destroyed shortly thereafter. You can do that when prototyping algorithms in script languages.

Thus it is better to have the derivative function have signature

void f( double * dy, double * y, double t);

where dimension and constants are external constants (or add on a parameter array to the list of arguments)

Then you use that in the rk4 loop as

f(k1,  y, t      );
for(i=0; i<n; i++) yt[i] = y[i] + 0.5*h*k1[i];
f(k2, yt, t+0.5*h);
for(i=0; i<n; i++) yt[i] = y[i] + 0.5*h*k2[i];
f(k3, yt, t+0.5*h);
for(i=0; i<n; i++) yt[i] = y[i] +     h*k3[i];
f(k4, yt, t+h);
for(i=0; i<n; i++) y[i] = y[i] + h/6*(k1[i]+2*(k2[i]+k3[i])+k4[i]);

Of course you will want to ensure that you allocate the k1,k2,k3,k4,yt also only once per integration. This can happen in 3 ways:

  • the integration procedure contains the full loop, returning a list of points if necessary,
  • there is some structure containing the work arrays that is constructed before the first integration step,
  • or you use the newer dynamic stack allocation that should also not have a prohibitive allocation penalty.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM