Trying to access constructor variables in class functions

  • C/C++
  • Thread starter cppIStough
  • Start date
  • Tags
    Programming
In summary: RK4 {private:double x0, y0, x, h;public:RK4(double x0_, double y0_, double x_, double h_) {x0 = x0_; y0 = y0_; x = x_; h = h_;std::vector<double> vec_x, vec_y;double f(double x_, double y_) {return y_; }void rkf(std::vector<
  • #1
cppIStough
22
2
There's a lot here and i can cut it down if need, but im wondering how one access's vectors vec_y and vec_x (which initialize in the constructor) in the rkf function? See, ultimately id like to have the class perform the rkf function which modifies vec_x and vec_y (according the RungeKutta ode solver), so that through a class object i can access vec_x and vec_y without defining these vectors outside the class. Your help is appreciated

C++:
#include <iostream>
#include <vector>

class RK4 {
private:
double x0, y0, x, h;

public:
RK4(double x0_, double y0_, double x_, double h_)
    {
x0 = x0_;
        y0 = y0_;
        x = x_;
        h = h_;

        std::vector<double> vec_x, vec_y;

        int n = (x - x0)/h;
        for (int i = 0; i < n; ++i)
            {
vec_x[i] = i*h;
                vec_y[i] = y0;
            }
    }

double f(double x_, double y_) {
return y_;
    }

void rkf(std::vector<double> & vec_x, std::vector<double> & vec_y) {
for(int i = 0; i < vec_x.size(); ++i) {
double k1 = f(vec_x[i], vec_y[i]);
            double k2 = f(vec_x[i] + h / 2, vec_y[i] + h * k1 / 2);
            double k3 = f(vec_x[i] + h / 2, vec_y[i] + h * k2 / 2);
            double k4 = f(vec_x[i] + h, vec_y[i] + h * k3);
            vec_y[i+1] = vec_y[i] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
        }
    }
};

int main() {

double h = 0.0001;
    double x0 = 0;
    double y0 = 1;
    double x = 1;

    RK4 ob1(x0, y0, h, x);
    return 0;
}
 
Technology news on Phys.org
  • #2
:welcome:

cppIStough said:
im wondering how one access's vectors vec_y and vec_x (which initialize in the constructor) in the rkf function?
Currently you are declaring the two vectors inside the constructor as what is known as an auto (stack allocated) variable. This means when they go out of scope (here at the end of the constructor method) these two variables are destroyed again and are thus never accessible outside the constructor. To have the two vectors as member variables instead you can move their declaration to be with the other such variables at line 6. If you do so it mean they will now be constructed just before the call enters the constructor method and will be destructed when the rk4 instance is destroyed.

Note that you have other issues in your code, like indexing into the two vectors before giving them a length (you can here use vec_x.resize(n) before indexing into them to ensure the vector has n elements all initialized to zero, or similar). I assume you are new to C++ and the RK-methods so you will probably have a lot on your hands learning both at the same time.
 
Last edited:
  • Like
Likes cppIStough and FactChecker
  • #3
Filip Larsen said:
:welcome:Currently you are declaring the two vectors inside the constructor as what is known as an auto (stack allocated) variable. This means when they go out of scope (here at the end of the constructor method) these two variables are destroyed again and are thus never accessible outside the constructor. To have the two vectors as member variables instead you can move their declaration to be with the other such variables at line 6. If you do so it mean they will now be constructed just before the call enters the constructor method and will be destructed when the rk4 instance is destroyed.

Note that you have other issues in your code, like indexing into the two vectors before giving them a length (you can here use vec_x.resize(n) before indexing into them to ensure the vector has n elements all initialized to zero, or similar). I assume you are new to C++ and the RK-methods so you will probably have a lot on your hands learning both at the same time.

Thanks, this is helpful. Notice my changes below (changed the rkf function input, moved the vectors out of the constructor and as public variables. However, I still have a question. It appears the vectors don't change their size in the constructor, owing to your comment about them leaving the scope. My question is, why put anything in the constructor if the moment we leave the scope it is destroyed?
C++:
#include <iostream>
#include <vector>

class RK4 {
private:
double x0, y0, x, h;

public:
std::vector<double> vec_x, vec_y;

    RK4(double x0_, double y0_, double x_, double h_)
    {
x0 = x0_;
        y0 = y0_;
        x = x_;
        h = h_;

        int n = (x - x0)/h;
        vec_x.resize(n);
        vec_y.resize(n);
        for (int i = 0; i < n; ++i)
            {
vec_x[i] = i*h;
                vec_y[i] = y0;
            }
    }

double f(double x_, double y_) {
return y_;
    }

void rkf() {
for(int i = 0; i < vec_x.size(); ++i) {
double k1 = f(vec_x[i], vec_y[i]);
            double k2 = f(vec_x[i] + h / 2, vec_y[i] + h * k1 / 2);
            double k3 = f(vec_x[i] + h / 2, vec_y[i] + h * k2 / 2);
            double k4 = f(vec_x[i] + h, vec_y[i] + h * k3);
            vec_y[i+1] = vec_y[i] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
        }
    }
};

int main() {

double h = 0.0001;
    double x0 = 0;
    double y0 = 1;
    double x = 1;

    RK4 ob1(x0, y0, h, x);
    ob1.rkf();
    for( auto v : ob1.vec_x)
    {
std::cout << v << "\n";
    }

return 0;
}
 
  • #4
cppIStough said:
My question is, why put anything in the constructor if the moment we leave the scope it is destroyed?
You might want some variables for intermediate calculations without worrying if those variables are already used for something in a higher scope. This keeps them separate from other variables with the same name.
 
  • Like
Likes cppIStough
  • #5
cppIStough said:
It appears the vectors don't change their size in the constructor, owing to your comment about them leaving the scope.
I am not sure if you talk about your first snippet or your newer second snippet?
In your second code snippet you have the two vectors as member variables, so their lifetime now should follow the lifetime of your RK4 instances (of which you create one, "ob1" in your main function). Since you are setting the size of the two vectors in the constructor they should retain that size. However, look careful on the order of your constructor arguments and compare with where you call it and you can perhaps see why you end up with n == 0.
 
  • Like
Likes cppIStough
  • #6
Filip Larsen said:
I am not sure if you talk about your first snippet or your newer second snippet?
In your second code snippet you have the two vectors as member variables, so their lifetime now should follow the lifetime of your RK4 instances (of which you create one, "ob1" in your main function). Since you are setting the size of the two vectors in the constructor they should retain that size. However, look careful on the order of your constructor arguments and compare with where you call it and you can perhaps see why you end up with n == 0.
Hah! Wow brain fart for sure! Worst part is my IDE shows me the implication of each function input (for those wondering line 50 and line 11 are clearly inconsistent). So to highlight what I've learned here: the constructor implies a scope, and stack-variables are destroyed when they leave the scope. Since the constructor scope does not exist after instantiating (right word?) the object, stack-variables declared there will be destroyed when other class methods are called.

One fix was to define the vectors in the class, and modify them in the constructor. Now I'm going to try and split this simple code up into a main function as well as .cpp and .h (I know rk4, but was trying to get my hands dirty with cpp). I'll post here if that's okay once I make those other files (I'll likely make mistakes)?
 
  • Like
Likes Filip Larsen
  • #7
cppIStough said:
brain fart for sure
It is a very common error that even professionals keep falling into, enabled by having a sequence of several positional parameter all of same type (here its double). Code for numerical computation is particularly prone to this since the purpose in life for most of the most such functions are to take a sequence of typical double parameters.

In C++ there is unfortunately no easy and concise way to make function and method signatures more robust against this (like for example named parameters available in some other languages). The best overall approach for a better design in C++ usually involves introducing named structs to group together variables that are related to each other or to the method which, together with C++20 designated initializers, come close to having named parameters.

cppIStough said:
instantiating (right word?)
Yes, instantiating, initialization and constructing is used interchangeably, with instantiating being a bit more programming language neutral and with initialization and constructing having very specific meanings in C++.
 
  • Like
Likes cppIStough
  • #8
Thanks for your help! Some nice clarity here. So after a little reworking I changed the file structure to look like this:
main file titled ODE.cpp :
C++:
#pragma once
#include "rk4.h"
#include "rk4.cpp"
#include <iostream>

int main() {

    double h = 0.0001;
    double x0 = 0;
    double y0 = 1;
    double x = 1;

    RK4 ob1(x0, y0, x, h);
    ob1.rkf();
    for (auto v : ob1.vec_y)
    {
        std::cout << v << "\n";
    }
    return 0;
}
header file titled rk4.h
C++:
#pragma once
#include <iostream>
#include <vector>

class RK4 {
private:
    double x0, y0, x, h;

public:
    std::vector<double> vec_x, vec_y;

    RK4(double x0_, double y0_, double x_, double h_);

    double f(double x_, double y_);

    void rkf();
};

cpp file titled rk4.cpp
C++:
#pragma once
#include "rk4.h"

RK4::RK4(double x0_, double y0_, double x_, double h_)
{
    x0 = x0_;
    y0 = y0_;
    x = x_;
    h = h_;

    int n = (x - x0) / h;
    vec_x.resize(n);
    vec_y.resize(n);
    for (int i = 0; i < n; ++i)
    {
        vec_x[i] = i * h;
        vec_y[i] = y0;
    }
}

double RK4::f(double x_, double y_)
{
    return y_;
}

void RK4::rkf()
{
    for (int i = 0; i < vec_x.size(); ++i) {
        double k1 = f(vec_x[i], vec_y[i]);
        double k2 = f(vec_x[i] + h / 2, vec_y[i] + h * k1 / 2);
        double k3 = f(vec_x[i] + h / 2, vec_y[i] + h * k2 / 2);
        double k4 = f(vec_x[i] + h, vec_y[i] + h * k3);
        vec_y[i + 1] = vec_y[i] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
    }
}

When running I get a linker error: 1>ODE.obj : error LNK2005: "public: __cdecl RK4::RK4(double,double,double,double)" (??0RK4@@QEAA@NNNN@Z) already defined in rk4.obj

I'm a little confused what the issue is here. Is it a Visual Studio problem, or am I missing something?
 
  • #9
It is standard practice to use a macro pre-processor variable in a header file to determine if it has already been included and not try to define its contents again. That way, you can include it in multiple files but the contents will not be defined again.

header file rk4.h:
#ifndef RK4_H_
#define RK4_H_
// Contents and definitions

#endif // RH4_H_
 
  • #10
FactChecker said:
It is standard practice to use a macro pre-processor variable in a header file to determine if it has already been included and not try to define its contents again. That way, you can include it in multiple files but the contents will not be defined again.

header file rk4.h:
#ifndef RK4_H_
#define RK4_H_
// Contents and definitions

#endif // RH4_H_
Notice I use #pragma once in each file though
 
  • #11
cppIStough said:
Notice I use #pragma once in each file though
I didn't notice that, and I was not familiar with it. Wikipedia says this: "On the other hand, #pragma once is not necessarily available in all compilers and its implementation is tricky and might not always be reliable."
If you try it with include guards, I would be interested to know if that makes the difference.
 
  • #12
In ODE.cpp you have this: #include "rk4.cpp".
It should NOT be there. #include directives are to be used only to include header files, not source files.
 
  • Like
Likes pbuk, cppIStough and FactChecker
  • #13
FactChecker said:
I didn't notice that, and I was not familiar with it. Wikipedia says this: "On the other hand, #pragma once is not necessarily available in all compilers and its implementation is tricky and might not always be reliable."
If you try it with include guards, I would be interested to know if that makes the difference.
Thanks for your comment. I was about to try the guards you recommended when I read @Mark44 comment. Turns out their suggestion was correct. So is this just a rule of thumb then, to never include .cpp files?

(Also, line 34 in rk4.cpp goes out of range, and should only go to size()-1, which I realized after @Mark44 suggestion enabled the program to compile)
 
  • Like
Likes FactChecker
  • #14
cppIStough said:
Thanks for your comment. I was about to try the guards you recommended when I read @Mark44 comment. Turns out their suggestion was correct. So is this just a rule of thumb then, to never include .cpp files?
It is generally better to only include the interface definition in the header file and then let the linker find the .cpp implementation in a library. That allows you to concentrate on the interface when you write the main program and allow the library to supply an implementation that is appropriate for a particular computer system.
But that is not a hard-and-fast rule. Sometimes you might want to divide up a program where you are making every part. You can skip making a library by including all the code. That will take you back to your original problem.
 
  • Like
Likes cppIStough
  • #15
cppIStough said:
Thanks for your comment. I was about to try the guards you recommended when I read @Mark44 comment. Turns out their suggestion was correct. So is this just a rule of thumb then, to never include .cpp files?
I can't find any rule about what sorts of files that can be included with the #include directive, but it's something I've known for about 30 years -- that only header files get included. If you have more than one source file (ie., files with .c or .cpp extensions) you inform the build tools by listing them in a make file, or listing them as part of a Visual Studio project.
 
  • Like
Likes cppIStough
  • #16
Mark44 said:
I can't find any rule about what sorts of files that can be included with the #include directive, but it's something I've known for about 30 years -- that only header files get included. If you have more than one source file (ie., files with .c or .cpp extensions) you inform the build tools by listing them in a make file, or listing them as part of a Visual Studio project.
Brilliant, this makes a lot of sense, thanks.

FactChecker said:
It is generally better to only include the interface definition in the header file and then let the linker find the .cpp implementation in a library. That allows you to concentrate on the interface when you write the main program and allow the library to supply an implementation that is appropriate for a particular computer system.
But that is not a hard-and-fast rule. Sometimes you might want to divide up a program where you are making every part. You can skip making a library by including all the code. That will take you back to your original problem.
Perfect, thanks for the insight here. It's nice to learn stuff like this because it's difficult to find in books/without human help.

I appreciate all your help. The program compiles and seems to print the correct results. That being said, if you saw anyhting that looked like bad practice or even that you would change, please tell me. I'm trying to learn to be a good cpp programer, and your help is great.
 
  • #17
cppIStough said:
Notice I use #pragma once in each file though
Just want to confirm that #pragma once works fine with VS, so include guards are only needed if the code needs to be compatible with old compilers. If I had to guess, I would say if you are using a modern C++ language variant anyway, e.g. C++17, then any compiler supporting this would likely also support the #pragma once instruction.
 
  • #18
cppIStough said:
So is this just a rule of thumb then, to never include .cpp files?
Usually one .cpp file corresponds to one translation unit. The compiler compiles each .cpp file as logically separate (even it if it compiles them all in one go), and then the linker links the resulting modules into an executable. So while you technically can include whatever valid C++ you like in a compilation unit (or header file for that matter) the compiler or people reading the code will likely get confused. And if the included code declares global symbols that is also included in another module it will, as you saw, result in linker error.

One example of including implementation though would be for templated classes, since these are usually compiled into other modules during compilation rather than linked later. To avoid confusion with .cpp files such implementation would be placed in differently named files, like .hpp. There are several libraries that are compile-time only, for example the Eigen library.
 
  • Like
Likes cppIStough
  • #19
Filip Larsen said:
Just want to confirm that #pragma once works fine with VS, so include guards are only needed if the code needs to be compatible with old compilers. If I had to guess, I would say if you are using a modern C++ language variant anyway, e.g. C++17, then any compiler supporting this would likely also support the #pragma once instruction.
But post #8 seems to indicate that it did not work. Was it being misused in some way in that post?

PS. This thread is now being used for a different question than its original question. That caused me some confusion.
 
  • #20
FactChecker said:
post #8 seems to indicate that it did not work. Was it being misused in some way in that post?
Well, the OP do have a #pragma once in the two .cpp files which is unneeded and possibly confusing to human readers since that pragma is meant for files that are supposed to be #included. However, technically it will still compile fine even if #pragma once are put into .cpp files by accident. The linker issue OP had was due to including one translation unit source into another unit thus creating duplicate symbols for the linker.
 
  • Like
Likes FactChecker
  • #21
Filip Larsen said:
Well, the OP do have a #pragma once in the two .cpp files which is unneeded and possibly confusing to human readers since that pragma is meant for files that are supposed to be #included. However, technically it will still compile fine even if #pragma once are put into .cpp files by accident. The linker issue OP had was due to including one translation unit source into another unit thus creating duplicate symbols for the linker.
Oh! I should have noticed immediately that it was a linker error message, not a compiler error.
 
  • #22
cppIStough said:
That being said, if you saw anyhting that looked like bad practice or even that you would change, please tell me.
  1. Be consistent with indentation: in #3 this is all over the place (although it is fine in #8, were you having problems with your IDE?).
  2. Only include files where you need them: you don't need #include <iostream> in rk4.cpp.
  3. Think about names for things. RK4 is OK for a class that implements a particular ODE integrator, but rkf() is not a good name for the method that performs the integration: integrate(), solve() or even execute() would be better.
  4. Does your code work when (x - x0) is not an integer multiple of h?
  5. Does your code always work when e.g. h = 0.1? Do you understand roundoff error?
  6. I would implement this so that the constructor accepted (a pointer to) the function as a parameter rather than being hard-coded into the integrator class.

cppIStough said:
I'm trying to learn to be a good cpp programer, and your help is great.
If you tell us why we can provide more focussed help:
  1. I want to write games.
  2. I want to write system software.
  3. I want to program microcontrollers.
  4. I want to write device drivers or other low-level software.
  5. I want to write programs for numerical computation.
  6. I believe that this will enable me to write programs that run really fast.
  7. Something else: what?
 
Last edited:
  • Like
Likes cppIStough and Mark44
  • #23
pbuk said:
Think about names for things. RK4 is OK for a class that implements a particular ODE integrator, but rkf() is not a good name for the method that performs the integration:
Indeed, my first thought upon seeing rkf() was the Runge-Kutta-Fehlberg method, which automatically adjusts the step size to adapt to the characteristics of the solution. I once taught a numerical methods course which included it. It was long enough ago that we used Fortran for it.
 
  • #24
pbuk said:
  1. Be consistent with indentation: in #3 this is all over the place (although it is fine in #8, were you having problems with your IDE?).
  2. Only include files where you need them: you don't need #include <iostream> in rk4.cpp.
  3. Think about names for things. RK4 is OK for a class that implements a particular ODE integrator, but rkf() is not a good name for the method that performs the integration: integrate(), solve() or even execute() would be better.
  4. Does your code work when (x - x0) is not an integer multiple of h?
  5. Does your code always work when e.g. h = 0.1? Do you understand roundoff error?
  6. I would implement this so that the constructor accepted (a pointer to) the function as a parameter rather than being hard-coded into the integrator class.
If you tell us why we can provide more focussed help:
  1. I want to write games.
  2. I want to write system software.
  3. I want to program microcontrollers.
  4. I want to write device drivers or other low-level software.
  5. I want to write programs for numerical computation.
  6. I believe that this will enable me to write programs that run really fast.
  7. Something else: what?
Thanks for this, very nice. My indentation is actually good on my IDE, I just now realized copy-paste didn't preserve that, which I apologize for. I should check for multiples of h. I did realize the roundoff error, and I'm familiar with it. I'll finesse the code here. Can you elaborate on the constructor? I'm very intersted.

And I am trying to learn cpp for numerical computation. There's of course other aspects, like getting and storing the data (IC's, BC's, etc) from a database and what not, but at the end of the day I'm trying to learn it becasue cpp is so much faster than non-compiled languages.
 
  • #25
cppIStough said:
I'm trying to learn it becasue cpp is so much faster than non-compiled languages.
That is not a good reason to learn C++:
  1. In 2023 there is no such thing as a (mainstream) "non-compiled language": look up JIT compilation.
  2. C++ uses exactly the same bindings as any other (mainstream) language for storing data in a file or database, communicating with other processes or over a network and sending data to a GPU and this is what 99% of software is doing 99% of the time it is not just waiting around for input, so what exactly are you expecting to be "so much faster"?

cppIStough said:
And I am trying to learn cpp for numerical computation.
If you still want to go with C++, take a look at the BOOST library, but I would recommend you look at Python and its SciPy library first.

cppIStough said:
Thanks for this, very nice. My indentation is actually good on my IDE, I just now realized copy-paste didn't preserve that, which I apologize for.
No need to apologise, just checking :smile:

cppIStough said:
I should check for multiples of h.
I'd do it a different way - rewrite RK4::rkf() so it just does one step and make h a parameter: you will find this makes things much easier later (what if I want a variable step size? what if I don't know what range to integrate over and instead have a terminating condition?).

cppIStough said:
I did realize the roundoff error, and I'm familiar with it.
Good: have you studied other topics in numerical analysis including convergence, stability, stiffness etc?

And finally...
cppIStough said:
Can you elaborate on the constructor? I'm very intersted.

C++:
// Instead of implementing f in rk4.cpp..
double RK4::f(double x_, double y_)
// ...add it to the constructor's arguments and pass it on instantiation.
RK4::RK4(double f_(double x, double y), ... )
{
    f = f_
    // ...
}
// Now in main.cpp we can implement our f(x) as a simple function
// (with a better name and without the trailing underscores in the
// variable names which don't make sense here).
double dydx_equals_y(double x, double y)
{
    return y;
}
// And pass the function to the constructor and integrate (again with
// some more descriptive names).
RK4 solver(dydx_equals_y, x0, y0, x, h);
solver.integrate();
Note that when we have functions as arguments these are actually pointers to functions but we don't have to use * or & because C++ inserts these automatically (passing a function by value wouldn't make any sense).
 
Last edited:
  • Like
Likes cppIStough
  • #26
I'm less down on C++ than some (haters gonna hate, hate, hate, hate) but I think your strategy maximizes the cons and minimizes the pros.

1. The thing we are trying to minimize is not execution time, but "time to science". If it takes youy an extra month to write code that runs in an hour instead of a day, have you won? And what if it's more like an hour instead of 70 minutes?

2. As far as I can tell, you are trying to develop two things at the same time: an RK4 solver and an application that uses it. Given that you're probably not the first to use R-K, I'd be inclined to find an existing package and use that. Now I only have one problem.

This has the added benefit is that if you need to switch from R-K to something else, your code is already factorized.
 
  • Like
Likes Tom.G and pbuk
  • #27
As a PS, I think that GPUs are not the magic that people who have never programmed one seem to think, but R-K of multiple objects is very well suited for that architecture: it's basically what they were built to do.

If you find a GPU implementation, you've solved not one but two problems. And if your concern was performance, this might make you very happy indeed.
 
  • #28
cppIStough said:
And I am trying to learn cpp for numerical computation. There's of course other aspects, like getting and storing the data (IC's, BC's, etc) from a database and what not, but at the end of the day I'm trying to learn it becasue cpp is so much faster than non-compiled languages.
I have been involved in professional C++ development in 30 years both for libraries, applications and systems, and can say that the main reason for choosing C++ is not to get the compiled code to run faster(1) or to make a rapid prototype (although both things are possible with the right approach), no the main reason has always been to get direct interoperability between different libraries and/or to be in full control of what a modern CPU can offer while having as few language-determined constrains forced on you.

And if you don't know what you are doing this means its very easy (read: almost certainly going to happen) to get lost in all the possibilities C++ offers and shoot yourself in the foot several times in the process. Compare this to other languages, like Python, that offers you less freedom to shoot yourself in the foot.

Edit: (1) Well, 30 years ago, yes, exectution speed was an issue. At university we did RKF78 solvers in C++ for simulating non-linear dissipative systems on big workstations running for days because there was no alternative within department budget. Back then only Fortran or C++ where really considered for numerical applications so the choice was easy.
 
Last edited:
  • Like
  • Love
Likes Vanadium 50 and pbuk
  • #29
pbuk said:
C++:
// Instead of implementing f in rk4.cpp..
double RK4::f(double x_, double y_)
// ...add it to the constructor's arguments and pass it on instantiation.
RK4::RK4(double f_(double x, double y), ... )
{
    f = f_
    // ...
}
// Now in main.cpp we can implement our f(x) as a simple function
// (with a better name and without the trailing underscores in the
// variable names which don't make sense here).
double dydx_equals_y(double x, double y)
{
    return y;
}
// And pass the function to the constructor and integrate (again with
// some more descriptive names).
RK4 solver(dydx_equals_y, x0, y0, x, h);
solver.integrate();
Note that when we have functions as arguments these are actually pointers to functions but we don't have to use * or & because C++ inserts these automatically (passing a function by value wouldn't make any sense).
In line 18 doesn't
dydx_equals_y
need to have two doubles to operate on?

And thanks for the other feedback. I do appreciate your time. You know it's funny because after rereading my code I was wondering why I defined f in rk4. Really should be independent of that class.

Also, do I need to declare f at all before having it in the constructor?
 
  • #30
cppIStough said:
In line 18 doesn't dydx_equals_y need to have two doubles to operate on?

In line 18 we are simply passing a pointer to dydx_equals_y so that the RK4 instance can refer to it. It doesn't get called until lines 29-32 of your code in the post below, when it is indeed passed two doubles in each call.

cppIStough said:
C++:
#pragma once
#include "rk4.h"

RK4::RK4(double x0_, double y0_, double x_, double h_)
{
    x0 = x0_;
    y0 = y0_;
    x = x_;
    h = h_;

    int n = (x - x0) / h;
    vec_x.resize(n);
    vec_y.resize(n);
    for (int i = 0; i < n; ++i)
    {
        vec_x[i] = i * h;
        vec_y[i] = y0;
    }
}

double RK4::f(double x_, double y_)
{
    return y_;
}

void RK4::rkf()
{
    for (int i = 0; i < vec_x.size(); ++i) {
        double k1 = f(vec_x[i], vec_y[i]);
        double k2 = f(vec_x[i] + h / 2, vec_y[i] + h * k1 / 2);
        double k3 = f(vec_x[i] + h / 2, vec_y[i] + h * k2 / 2);
        double k4 = f(vec_x[i] + h, vec_y[i] + h * k3);
        vec_y[i + 1] = vec_y[i] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
    }
}

cppIStough said:
Also, do I need to declare f at all before having it in the constructor?
Not sure what you mean: it needs to be available to the member function RK4::rkf() (which I suggest would be better called RK4::solve()) and one way of doing that is to declare it as a member variable and pass it in the constructor which is what you are doing, but there are other ways - for instance you could just pass f (and the other arguments) directly to RK4::rkf(), which doesn't even need to be a member function - it could just be declared as static.

One of the problems with C++ is that if there are 15 different ways to do something, C++ will let you do it in 30 different ways.
 
  • Like
Likes cppIStough
  • #31
EDIT: I don't know why the cpp code box below isn't appearing in proper format. I'll fix it if I can, but I don't see anyhting wrong with the syntax to display properly.

Okay, this was helpful! Here's what I have (I'll clean up the rounding errors, and to answer a few posts ago, I am familiar with convergence, stiff ODEs, and I actually have heard of JIT compilation, though only about a year ago; I'm learning C++ because I want to know it (already know Python fairly well, though I'm sure compared to many on here I'm a novice), which is why I am picking some easy math problems to get my hands dirty)

But here's what I have so far in 3 files though I'm getting errors which I'll list below the three files this program consists of:

rk4.h
C++:
#pragma once
#include <iostream>
#include <vector>

class RK4 {
private:
    double x0, y0, x_end, h;

public:
    std::vector<double> vec_x, vec_y;

    void solve();
};

rk4.cpp
C++:
#include "rk4.h"

RK4::RK4(double f_(double x, double y), double x0_, double y0_, double x_end_, double h_)
{
    f = f_;

    x0 = x0_;
    y0 = y0_;
    x_end = x_end_;
    h = h_;

    int n = (x_end - x0) / h;
    vec_x.resize(n);
    vec_y.resize(n);
    for (int i = 0; i < n; ++i)
    {
        vec_x[i ] = i * h;
        vec_y[i ] = y0;
    }
}

void RK4::solve()
{
    for (int i = 0; i < vec_x.size() - 1; ++i) {
        double k1 = f(vec_x[i ], vec_y[i ]);
        double k2 = f(vec_x[i ] + h / 2, vec_y[i ] + h * k1 / 2);
        double k3 = f(vec_x[i ] + h / 2, vec_y[i ] + h * k2 / 2);
        double k4 = f(vec_x[i ] + h, vec_y[i ] + h * k3);
        vec_y[i + 1] = vec_y[i ] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
    }
}

ODE.cpp
C++:
#include "rk4.h"
#include <iostream>

double dydx_equals(double x, double y)
{
    return y;
}

int main() {

    double h = 0.01;
    double x0 = 0.;
    double y0 = 1.;
    double x_end = 1.;

    RK4 solver(dydx_equals, x0, y0, x_end, h);
    solver.solve();

    for (auto v : solver.vec_y)
    {
        std::cout << v << "\n";
    }
    return 0;
}

In rk4.cpp there is an error with the constructor stating "no instance of overloaded function RK4::RK4" which I'm confused on. Another error in this file is with the f = f_, which reads "identifier "f" is undefined, which is why I thought I may need to declare it, but am unsure how since the function is not a member of RK4. Any help would be awesome!
 
Last edited by a moderator:
  • #32
cppIStough said:
EDIT: I don't know why the cpp code box below isn't appearing in proper format. I'll fix it if I can, but I don't see anyhting wrong with the syntax to display properly.
It's because of the [i]: this is formatting code for italics here so it breaks the formatter. Your code has now been fixed up by a moderator but in future it is best to use j, i0, ii or whatever for index counts here.

cppIStough said:
Okay, this was helpful! Here's what I have (I'll clean up the rounding errors, and to answer a few posts ago, I am familiar with convergence, stiff ODEs, and I actually have heard of JIT compilation, though only about a year ago; I'm learning C++ because I want to know it (already know Python fairly well, though I'm sure compared to many on here I'm a novice), which is why I am picking some easy math problems to get my hands dirty)
That's all good. Another good way to get into C++ IMHO is by playing with a microcontroller kit - also adds experience in hardware and electronics to your portfolio (or gives you some fun playing with these things if they are already an interest).

cppIStough said:
In rk4.cpp there is an error with the constructor stating "no instance of overloaded function RK4::RK4" which I'm confused on.
That's because you haven't declared RK4:RK4 in your header file:
rk4.h
C++:
class RK4
{
public:
    std::vector<double> vec_x, vec_y;
    RK4 (double f_(double x, double y), double x0_, double y0_, double x_end_, double h_);
}

cppIStough said:
Another error in this file is with the f = f_, which reads "identifier "f" is undefined, which is why I thought I may need to declare it, but am unsure how since the function is not a member of RK4.
In your original code, f was a member of RK4, and the way you have written the constructor you are still trying to initialize it as a member so if you want to do it that way you should declare it in the header file.

But you don't have to do it that way, I was thinking of a pattern more like this:
C++:
// rk4.h
class RK4
{
private:
    // We don't need any of these private variables any more.
public:
    // We don't need to define the constructor any more because it doesn't need to do anything.
    std::vector<double> vec_x, vec_y;
    void solve (double f(double x, double y), double x0, double y0, double x_end, double h);
}

// rk4.cpp
    void solve(double f(double x, double y), double x0, double y0, double x_end, double h)
    {
        // Build the vectors.
        // Do the solution.
    }
 
Last edited by a moderator:
  • Like
Likes cppIStough
  • #33
pbuk said:
It's because of the [i]: this is formatting code for italics here so it breaks the formatter. Your code has now been fixed up by a moderator but in future it is best to use j, i0, ii or whatever for index counts here.
It's been fixed now. An index variable i can be used, provided that you add a space before or after the letter i. That way the MathJax interpreter doesn't convert the 'i' in brackets to the BBCode delimiter for italics.
 
  • Like
Likes cppIStough
  • #34
pbuk said:
It's because of the [i]: this is formatting code for italics here so it breaks the formatter. Your code has now been fixed up by a moderator but in future it is best to use j, i0, ii or whatever for index counts here.That's all good. Another good way to get into C++ IMHO is by playing with a microcontroller kit - also adds experience in hardware and electronics to your portfolio (or gives you some fun playing with these things if they are already an interest).That's because you haven't declared RK4:RK4 in your header file:
rk4.h
C++:
class RK4
{
public:
    std::vector<double> vec_x, vec_y;
    RK4 (double f_(double x, double y), double x0_, double y0_, double x_end_, double h_);
}
In your original code, f was a member of RK4, and the way you have written the constructor you are still trying to initialize it as a member so if you want to do it that way you should declare it in the header file.

But you don't have to do it that way, I was thinking of a pattern more like this:
C++:
// rk4.h
class RK4
{
private:
    // We don't need any of these private variables any more.
public:
    // We don't need to define the constructor any more because it doesn't need to do anything.
    std::vector<double> vec_x, vec_y;
    void solve (double f(double x, double y), double x0, double y0, double x_end, double h);
}

// rk4.cpp
    void solve(double f(double x, double y), double x0, double y0, double x_end, double h)
    {
        // Build the vectors.
        // Do the solution.
    }
Okay great, this makes a lot of sense. I'll polish up the roundoff errors but here's what we have (which works and makes a ton of sense: thanks!!!). Only difference, which I think you intended, was to have solve not output a void, but instead output the solution vector (or were you imagining this differently? Which is preferred to you?)

rk4.h
C++:
#pragma once
#include <iostream>
#include <vector>class RK4 {
public:
    std::vector<double> vec_x, vec_y;

    std::vector<double> solve(double f(double x, double y), double x0, double y0, double x_end, double h);
};

rk4.cpp
C++:
#include "rk4.h"std::vector<double> RK4::solve(double f(double x, double y), double x0, double y0, double x_end, double h)
{
    int n = (x_end - x0) / h;
    vec_x.resize(n);
    vec_y.resize(n);

    for (int ii = 0; ii < n; ++ii)
    {
        vec_x[ii] = ii * h;
        vec_y[ii] = y0;
    }

    for (int ii = 0; i < vec_x.size() - 1; ++ii) {
        double k1 = f(vec_x[ii], vec_y[ii]);
        double k2 = f(vec_x[ii] + h / 2, vec_y[ii] + h * k1 / 2);
        double k3 = f(vec_x[ii] + h / 2, vec_y[ii] + h * k2 / 2);
        double k4 = f(vec_x[ii] + h, vec_y[ii] + h * k3);
        vec_y[ii + 1] = vec_y[ii] + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4);
    }

    return vec_y;
}

ODE.cpp
C++:
#include "rk4.h"
#include <iostream>

double dydx_equals(double x, double y)
{
    return y;
}

int main() {

    double h = 0.01;
    double x0 = 0.;
    double y0 = 1.;
    double x_end = 1.;

    RK4 solver;
    auto soln = solver.solve(dydx_equals, x0, y0, x_end, h);

    for (auto v : soln)
    {
        std::cout << v << "\n";
    }
    return 0;
}

So thinking about expanding on top of this, it seems it would make more sense to make the "grid" in a separate class from RK4 since it's independent of Runge Kutta. What do you think? I know this program is small and simple but I want to program it in an intelligent manner.

And to reiterate what you said above, passing a function name with no args into a constructor/function/etc is a pointer to the function?
 
  • #35
cppIStough said:
And to reiterate what you said above, passing a function name with no args into a constructor/function/etc is a pointer to the function?
Yes.
 
  • Like
Likes cppIStough and pbuk

Similar threads

Replies
15
Views
2K
Replies
23
Views
2K
Replies
1
Views
2K
Replies
2
Views
3K
Replies
89
Views
5K
Replies
13
Views
5K
Replies
1
Views
2K
Back
Top