By Stefan Verhoeven, Faruk Diblen, Jurriaan H. Spaaks, Adam Belloum, and Christiaan Meijer.
Let's say you have some C++ code laying around that you would like to make available to a wider audience, by putting it on the web. Until recently, this used to be pretty difficult, and may even have required reimplementation of the software in JavaScript, the programming language that browsers use.
Wouldn't it be great if you could run your existing C++ code on the web with only minor effort?
That way, loads more people would be able to see your results, interact with your algorithm, and apply it for their own purposes.
In this blog, we'll show you how to take a simple algorithm written in C++ and make it available as a web application. Subsequent blogs in this series will expand on the current one by laying out more advanced topics, specifically how to make the app interactive, how to visualize the results, and how to deal with long running tasks.
So today's aim is to have a simple web app that determines the root of a mathematical function 2x^3 - 4x^2 + 6, i.e. the value of x where y = 0.
Function 2x^3 - 4x^2 + 6.
For this, we'll use an iterative method known as the Newton-Raphson root finding
method. Remember Newton? Quiet fellow, fabulous hair? Yes, that Newton.
The way Newton-Raphson works is, you give it the equation whose root you want to find, along with the derivative of
that equation. Then you take an initial_guess
of what you think the value of the root could be, then let the method
iterate towards the solution. The solution is approximate within a tolerance
, which you can also set. Anyway, the
algorithm is written C++, but with some trickery, we'll be able to use that C++ code from the browser, without the
need to port it first!
Newton (and his hair). Image courtesy of Wikimedia Commons.
Now before you say "That'll be so much slower than running it native!" or "C++ from the browser? Impossible!", just hold your horses for a sec. With the right tools, it is possible to run C++ code in the browser, with an acceptable performance penalty. For example, Gabriel Cuvillier was able to run the video game Doom 3 in the browser. He was able to do this by compiling the game's source code into WebAssembly, a low-level language that browsers can run. And if it works for video games, it will likely work for your research software, too.
Hold your horses. Image courtesy of Luciano Marelli via pixabay.
OK, now that you're fully on board with this, let's get to it. Here's a list of what we need:
- We are going to write a small HTML page, so you will need basic knowledge of HTML and JavaScript.
- Some C++ code to illustrate the process. We'll use our Newton-Raphson C++ code.
- A program that can take our existing C++ code and compile it into a WebAssembly module. For this, we'll use
Emscripten's
emcc
compiler, the most popular C++ to WebAssembly compiler of the bunch. - To use the WebAssembly functionality from JavaScript, a binding is required. The binding will map C++ constructs to their JavaScript equivalent and back. For this, we'll use embind.
- A web server to serve our files. We'll use Python 3's
http.server
, but other web servers work equally well.
Here is the equation whose root we want to find, along with its derivative, since that's what Newton-Raphson requires:
#ifndef H_PROBLEM_HPP
#define H_PROBLEM_HPP
float equation(float x);
float derivative(float x);
#endif
File: problem.hpp
// An example equation
float equation(float x) {
return 2 * x * x * x - 4 * x * x + 6;
}
// Derivative of the above equation
float derivative(float x) {
return 6 * x * x - 8 * x;
}
File: problem.cpp
The snippet below shows the contents of the file newtonraphson.hpp
. It is the header file for the Newton-Raphson
iterative root finding algorithm. It defines a class named NewtonRaphson
. Besides the
constructor method NewtonRaphson(float tolerance_in)
, NewtonRaphson
has one other public method, solve
,
which takes a float
, and returns another float
. Furthermore, NewtonRaphson
also has a private member,
tolerance
of type float
, which is used to store the class instance's private data.
#ifndef H_NEWTONRAPHSON_HPP
#define H_NEWTONRAPHSON_HPP
class NewtonRaphson {
public:
NewtonRaphson(float tolerance_in);
float solve(float initial_guess);
private:
float tolerance;
};
#endif
File: newtonraphson.hpp.
File newtonraphson.cpp
contains the corresponding implementation:
#include "newtonraphson.hpp"
#include "problem.hpp"
#include <cmath>
// Define the constructor method of NewtonRaphson instances
NewtonRaphson::NewtonRaphson(float tolerance_in) : tolerance(tolerance_in) {}
// Define the 'solve' method of NewtonRaphson instances
float NewtonRaphson::solve(float initial_guess) {
float x = initial_guess;
float delta_x = 0;
do {
delta_x = equation(x) / derivative(x);
x = x - delta_x;
} while (std::abs(delta_x) >= tolerance);
return x;
};
File: newtonraphson.cpp.
From this definition, NewtonRaphson
instances need to be initialized with a value for tolerance_in
, which is then
stored as the private member tolerance
. Once the object instance has been constructed, users can call its solve
method to iteratively find equation
's root, with equation
and its derivative
being imported from
problem.hpp
via the include
line near the top.
The following code is a minimal command line program that we can use to check if everything is working correctly:
#include <iostream>
#include <iomanip>
#include "newtonraphson.hpp"
int main() {
float initial_guess = -4;
float tolerance = 0.001;
NewtonRaphson newtonraphson(tolerance);
float root = newtonraphson.solve(initial_guess);
std::cout << "Function root is approximately at x = ";
std::cout << std::fixed << std::setprecision(2) << root << std::endl;
return 0;
}
File: cli.cpp.
Our command line program can be compiled with:
g++ -o cli.exe problem.cpp newtonraphson.cpp cli.cpp
Subsequently running it should give the following output:
./cli.exe
The value of the root is : -1.00
Now we're ready to move on to the WebAssembly part.
To use the Newton-Raphson code from JavaScript, we'll need to define the bindings file. The binding allows compiled code to be called from JavaScript. For our Newton-Raphson code, the binding file looks like this:
#include <emscripten/bind.h>
#include "newtonraphson.hpp"
using namespace emscripten;
EMSCRIPTEN_BINDINGS(newtonraphson) {
class_<NewtonRaphson>("NewtonRaphson")
.constructor<float>()
.function("solve", &NewtonRaphson::solve)
;
}
File: bindings.cpp.
The binding file uses embind
binding
statements to expose the
NewtonRaphson
class, its constructor method, as well as its public method solve
.
First we need to download and install Emscripten to get the compiler.
The Newton-Raphson source and its binding can be compiled into a WebAssembly module with Emscripten's emcc
compiler, as follows:
emcc -I. -o newtonraphson.js -Oz -s MODULARIZE=1 \
-s EXPORT_NAME=createModule --bind problem.cpp newtonraphson.cpp bindings.cpp
This will generate a WebAssembly module newtonraphson.wasm
, along with a JavaScript file newtonraphson.js
. We also export the createModule
JavaScript function in the compile command so it can be used to load and initialize the WebAssembly module.
Using this JavaScript library, we can find the root of the mathematical function, and subsequently display its value with the
following HTML:
<html>
<head>
<!-- Load WebAssembly module -->
<script type="text/javascript" src="newtonraphson.js"></script>
</head>
<body>
<div>
Function root is approximately at x =
<span id="answer"/>
</div>
<script>
// Wait for module to initialize,
createModule().then(({NewtonRaphson}) => {
// Hardcoded input values
const initial_guess = -4;
const tolerance = 0.001;
// Perform computation
const newtonraphson = new NewtonRaphson(tolerance);
const root = newtonraphson.solve(initial_guess);
// Write the value of 'root' to the tag whose 'id' is equal to "answer"
document.getElementById("answer").innerHTML = root.toFixed(2);
});
</script>
</body>
</html>
File: index.html.
We'll need a web server to display the HTML page in a web browser. For this, we'll use the http.server module from Python 3 to host all files in the current directory on port 8000, like so:
# change to directory with index.html, newtonraphson.* files
python3 -m http.server 8000
From the figure at the top of the article, the root of the equation should be at x = -1.00
. Visit
http://localhost:8000/ to see if your browser shows the correct result.
The resulting page if everything works. See the live version of the app on GitHub pages.
- We wrote a simple algorithm in C++
- We defined the JavaScript interface by writing Emscripten bindings
- We compiled the algorithm and bindings to a WebAssembly module with Emscripten compiler
- We ran the algorithm in a web browser using some JavaScript to talk to the WebAssembly module.
The nice thing about this solution is that we don't need expensive infrastructure to perform computation as the computation is done in the user's web browser--we just need somewhere to host the files.
This blog was written by the Generalization Team of the Netherlands eScience Center. The team consists of Stefan Verhoeven, Faruk Diblen, Jurriaan H. Spaaks, Adam Belloum and Christiaan Meijer. Feel free to get in touch with the generalization team at [email protected].
In upcoming blogs we will cover:
- Help! My C++ web app is not responding: how to perform computations without blocking the user interface.
- Interact with your C++ web app using React forms: how to let the user supply their own input values for
tolerance
andinitial_guess
. - Spice up your C++ web app with visualizations: how to visualize data from the algorithm.
We'll wrap up the series in a final blog that combines the topics of the whole series in a full-featured web application. If you're curious what that's going to look like, make sure to check out the live demo on GitHub Pages.
If you enjoyed this article, leave a comment and give us a clap!
These blogs were written as part of the "Passing XSAMS" project. To learn more about the project, check out its project page.
Thank you to our proof readers Jan van Dijk, Daan Boer, Lourens Veen and Patrick Bos.