We have compiled a C++ program and produced an executable myvicsek. We are running it from the shell and then retrieving the date from Python. Could we streamline the process a bit?
The first thing we could do is tho make our makefile take some external parameters from input, so that we do not need to recompile it every time we change a parameter.
This is easily done: the main function takes default inputs int argc, char* argv[] which are :
Let us consider the simple case in which we only want one parameter in input, the noise strength.
We can do the following:
#include <iostream>
#include <cstdlib> // For std::stof (string to float)
int main(int argc, char* argv[]) {
// Convert the string argument to a float
double noiseStrength = std::stof(argv[1]);
// rest of main
// ...
return 0;
}Once this is done, we can recompile and run
to run our program at noise 0.15. But could we do this from python instead?
A dedicated module exists, called subprocess. It allows us to spawn a new process (our C++ program) from another process (e.g. a Python program).
import subprocess
# Define the command and arguments inside a list
command = ['./myvicsek', '0.14']
# Call the C++ program via subprocess
result = subprocess.run(command, capture_output=True, text=True)
# Print the output
print("Standard Output:", result.stdout)
print("Standard Error:", result.stderr)Task 1: Get input parameters and try subprocess
Modify main.cpp to take the noise in input, recompile and run the C++ program from a python program (or a jupyter notebook) using subprocess.
Even more interesting would be to hide all this technical detail and simply be able to do the following:
This code assumes that in our module pyvicsek there is something named Wrapper which takes a named parameter noise and a has an attached method called run(). Byt clearly this does not exist… yet!.
To make the code above valid we simply need to create a custom Python class. Classes in Python are very similar to C++, but much simpler.
The following analogies hold:
this is conventionally called self. and -> are just .__init__()Python classes are in general quite simpler than their C++ counterparts: there is no need for header files.
Our simple Wrapper class does not need to do much: it takes a parameter in input and then runs the code. However we need to give it some structure
class Wrapper:
def __init__(self, noise=0.1):
# code to initalise the object
def run(self):
# code to runNotice that the two methods (member functions) take self explicitly as their first parameter: this makes it possible for them to directly refer to the class instance inside their code. For example, the constructor __init__ can store the noise level by
and the method run() can refer to it, for example by using subprocess to run a local executable
def run(self):
command = ["absolute/path/to/myvicsek", str(self.noise)]
result = subprocess.run(command, capture_output=True, text=True)
return resultTask 2: Create your Wrapper class
Modify your pyvicsek.py so that it only contains function definitions and the definition of a class Wrapper following the hints above.
You can find the path to the absolute path to the current directory form the terminal using
From a Jupyter notebook (or , alternatively, a new Python script) import the pyvicsek module and create an instance of the Wrapper class and run a simulation.