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 run
Notice 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 result
Task 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.