<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://fruzsinaagocs.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://fruzsinaagocs.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-10-09T16:42:08+00:00</updated><id>https://fruzsinaagocs.github.io/feed.xml</id><title type="html">Fruzsina J. Agocs</title><subtitle>Personal website of Dr Fruzsina Julia Agocs. </subtitle><entry><title type="html">Python wrapper for C++ projects</title><link href="https://fruzsinaagocs.github.io/blog/2019/pythonwrapper/" rel="alternate" type="text/html" title="Python wrapper for C++ projects"/><published>2019-12-18T00:00:00+00:00</published><updated>2019-12-18T00:00:00+00:00</updated><id>https://fruzsinaagocs.github.io/blog/2019/pythonwrapper</id><content type="html" xml:base="https://fruzsinaagocs.github.io/blog/2019/pythonwrapper/"><![CDATA[<p><strong>TL;DR: I wrote an update to Dan Foreman-Mackey’s template for wrapping C functions in Python.</strong></p> <p>A while ago I had to wrap some C++ code (belonging to the numerical solver, oscode, I developed) in Python.</p> <p>My supervisor suggested using Dan Foreman-Mackey’s <a href="https://dfm.io/posts/python-c-extensions/">blogpost</a> as a starting point, and I found it extremely useful. It gives an example of how to wrap a function using the <a href="https://docs.python.org/3/c-api/index.html">Python-C API</a> <em>and a detailed explanation</em>. However, since it has been posted, Python bumped from 2.x to 3.x, with support for the popular 2.7 <a href="https://www.python.org/dev/peps/pep-0373/">having ended in January 2020</a>. As suggested by the change in first digit in the version number, some of the changes are major, including some changes in how one should use numpy in the Python-C API. <a href="https://numpy.org">Numpy</a> is such a commonly used library that I thought it would be worth putting an update out there for anyone facing the (terrifying) API for the first time. It is worth mentioning that a commenter has also thought of doing this, see their code <a href="https://gist.github.com/douglas-larocca/099bf7460d853abb7c17">here</a>.</p> <h2 id="the-directory-structure">The directory structure</h2> <p>Before we get into the wrapping process, this graph show the overall file structure of this project, which you can come back to for reference. The C++ code’s name is <code class="language-plaintext highlighter-rouge">oscode</code>, and we’ll call the Python interface to it <code class="language-plaintext highlighter-rouge">pyoscode</code>.</p> <div class="row justify-content-sm-center"> <div class="col-sm-4 mt-3 mt-md-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/file-structure-480.webp 480w,/assets/img/file-structure-800.webp 800w,/assets/img/file-structure-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/file-structure.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </div> </div> <div class="caption"> An illustration of the directory structure inside the repository. </div> <h2 id="the-cc-code">The C/C++ code</h2> <p>In this example we will wrap <em>oscode</em>, which is a header-only project. This means that all code resides in <code class="language-plaintext highlighter-rouge">.hpp</code> header files and for use in C++, one only needs to download these files and it is not necessary to compile them with <code class="language-plaintext highlighter-rouge">make</code>. It also means that declarations and definitions reside in the same file, for example in <code class="language-plaintext highlighter-rouge">include/solver.hpp</code>:</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
#include</span> <span class="cpf">"system.hpp"</span><span class="cp">
</span><span class="c1">// Various other includes...</span>

<span class="c1">// Class declaration</span>
<span class="k">class</span> <span class="nc">Solution</span>
<span class="p">{</span>
    <span class="nl">public:</span>
    <span class="c1">// Constructor of the class "Solution"</span>
    <span class="n">Solution</span><span class="p">(</span><span class="n">de_system</span> <span class="o">&amp;</span><span class="n">de_sys</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">x0</span><span class="p">,</span>
             <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">dx0</span><span class="p">,</span> <span class="kt">double</span> <span class="n">t_i</span><span class="p">,</span> <span class="kt">double</span> <span class="n">t_f</span><span class="p">,</span>
             <span class="kt">int</span> <span class="n">o</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="kt">double</span> <span class="n">r_tol</span><span class="o">=</span><span class="mf">1e-4</span><span class="p">,</span> <span class="kt">double</span> <span class="n">a_tol</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span>
             <span class="kt">double</span> <span class="n">h_0</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">full_output</span><span class="o">=</span><span class="s">""</span><span class="p">);</span>
    <span class="c1">// Method to solve the ODE</span>
    <span class="kt">void</span> <span class="n">solve</span><span class="p">();</span>
    <span class="c1">// Attributes that will contain the solution and other </span>
    <span class="c1">// info about the run</span>
    <span class="kt">int</span> <span class="n">ssteps</span><span class="p">,</span> <span class="n">totsteps</span><span class="p">,</span> <span class="n">wkbsteps</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&gt;</span> <span class="n">sol</span><span class="p">,</span> <span class="n">dsol</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">times</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">wkbs</span><span class="p">;</span>

    <span class="nl">private:</span>
    <span class="c1">// Rest of the class attributes and methods declared</span>
<span class="p">}</span>

<span class="c1">// Class definition</span>
<span class="n">Solution</span><span class="o">::</span><span class="n">Solution</span><span class="p">(</span><span class="n">de_system</span> <span class="o">&amp;</span><span class="n">de_sys</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">x0</span><span class="p">,</span>
                   <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">dx0</span><span class="p">,</span> <span class="kt">double</span> <span class="n">t_i</span><span class="p">,</span> <span class="kt">double</span> <span class="n">t_f</span><span class="p">,</span>
                   <span class="kt">int</span> <span class="n">o</span><span class="p">,</span> <span class="kt">double</span> <span class="n">r_tol</span><span class="p">,</span> <span class="kt">double</span> <span class="n">a_tol</span><span class="p">,</span> <span class="kt">double</span> <span class="n">h_0</span><span class="p">,</span>
                   <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">full_output</span><span class="p">){</span>

    <span class="c1">// Things that happen on class initialization</span>
<span class="p">}</span>

<span class="c1">// Class methods</span>
<span class="kt">void</span> <span class="n">Solution</span><span class="o">::</span><span class="n">solve</span><span class="p">(){</span>

    <span class="c1">// Things that happen when solve() is called</span>
<span class="p">}</span>

</code></pre></div></div> <p>Let’s run through what the above code does:</p> <ul> <li><code class="language-plaintext highlighter-rouge">#pragma once</code> makes sure this file is only included once in a single compilation. It is a <a href="https://en.wikipedia.org/wiki/Pragma_onceA">preprocessor directive</a>.</li> <li>We then include the header <code class="language-plaintext highlighter-rouge">include/system.hpp</code>, which contains a class called <code class="language-plaintext highlighter-rouge">de_system</code>. This stores all information about the ODE system the user wishes to solve, and will be an input to the solver function (<code class="language-plaintext highlighter-rouge">Solution::solve()</code>).</li> <li>Then we declare the class <code class="language-plaintext highlighter-rouge">Solution</code>. This is the module that carries out the ODE solving and stores information about the solution which the user can then retrieve. It has some methods and attributes: <ul> <li>It has a constructor, which has the same function as the <code class="language-plaintext highlighter-rouge">__init__()</code> in Python, it gets called when the class is initialized. As you see it has several inputs: the ODE itself, stored in <code class="language-plaintext highlighter-rouge">de_system</code>; the initial conditions <code class="language-plaintext highlighter-rouge">x0</code> and <code class="language-plaintext highlighter-rouge">dx0</code>; the integration limits <code class="language-plaintext highlighter-rouge">t_i</code> and <code class="language-plaintext highlighter-rouge">t_f</code>; them some precision parameters; and a string <code class="language-plaintext highlighter-rouge">full_output</code> containing the path to a file in which the output of the run is written. All parameters that have some default value set, e.g. <code class="language-plaintext highlighter-rouge">double h_0=1</code>, are optional.</li> <li>It has a method called <code class="language-plaintext highlighter-rouge">solve()</code>, which does the solving of the ODE.</li> <li>It has some empty lists containing the solution, its derivative, and some more information about the run.</li> </ul> </li> <li>In the same file we have the class definition, i.e. the actual content of the function <code class="language-plaintext highlighter-rouge">Solution::Solution()</code> and its method <code class="language-plaintext highlighter-rouge">Solution::solve()</code>.</li> </ul> <p>Our goal is to wrap the <code class="language-plaintext highlighter-rouge">Solution::solve()</code> function, but we’ll have to, in the wrapper, first define the differential equation via the <code class="language-plaintext highlighter-rouge">de_system</code> class, then create a <code class="language-plaintext highlighter-rouge">Solution</code> instance with the required initial conditions, tolerance requirements, etc., and only then call <code class="language-plaintext highlighter-rouge">solve()</code> on that instance.</p> <h2 id="the-wrapper">The wrapper</h2> <p>In our case the wrapper will consist of the following files, whose names start with an underscore by convention:</p> <ul> <li><code class="language-plaintext highlighter-rouge">pyoscode/_python.hpp</code>,</li> <li><code class="language-plaintext highlighter-rouge">pyoscode/_pyoscode.hpp</code>,</li> <li><code class="language-plaintext highlighter-rouge">pyoscode/_pyoscode.cpp</code>,</li> </ul> <p>and the ‘interface’,</p> <ul> <li><code class="language-plaintext highlighter-rouge">pyoscode/__init__.py</code>,</li> </ul> <p>Let’s start with <code class="language-plaintext highlighter-rouge">_python.hpp</code>.</p> <h3 id="_pythonhpp"><code class="language-plaintext highlighter-rouge">_python.hpp</code></h3> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
#include</span> <span class="cpf">&lt;Python.h&gt;</span><span class="cp">
#if PY_MAJOR_VERSION &gt;=3
#define PYTHON3
#endif
</span></code></pre></div></div> <p>The only role of this file is to check the version of Python we’re using and set a flag (<code class="language-plaintext highlighter-rouge">PYTHON3</code>) accordingly. Other files will include this one to know the value of the flag, which is important because the syntax of the wrapper is different for Python 2.x and 3.x.</p> <h3 id="_pyoscodehpp"><code class="language-plaintext highlighter-rouge">_pyoscode.hpp</code></h3> <p><code class="language-plaintext highlighter-rouge">_pyoscode.cpp</code> contains the functions wrapping the C++ functionality, and the corresponding header <code class="language-plaintext highlighter-rouge">_pyoscode.hpp</code> declares those functions. Some of these functions are special, as we’ll see below. So let’s start declaring in <code class="language-plaintext highlighter-rouge">_pyoscode.hpp</code>:</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
#include</span> <span class="cpf">&lt;array&gt;</span><span class="cp">
#include</span> <span class="cpf">"_python.hpp"</span><span class="cp">
</span><span class="c1">// Many more includes...</span>

<span class="c1">// Docstring for the module</span>
<span class="k">static</span> <span class="kt">char</span> <span class="n">module_docstring</span><span class="p">[]</span> <span class="o">=</span> 
<span class="s">"pyoscode: this module provides an interface for oscode, for solving oscillatory ordinary differential equations with the RKWKB method."</span><span class="p">;</span>

<span class="c1">// Docstring for the Solution::solve() function</span>
<span class="k">static</span> <span class="kt">char</span> <span class="n">solve_docstring</span><span class="p">[]</span> <span class="o">=</span>
<span class="s">"Runs the solver"</span><span class="p">;</span>

<span class="c1">// Available functions in the pyoscode module</span>
<span class="k">static</span> <span class="n">PyObject</span> <span class="o">*</span><span class="nf">_pyoscode_solve</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">PyObject</span>
<span class="o">*</span><span class="n">kwargs</span><span class="p">);</span>

<span class="c1">// Module interface</span>
<span class="k">static</span> <span class="n">PyMethodDef</span> <span class="n">module_methods</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span><span class="s">"solve"</span><span class="p">,</span> <span class="p">(</span><span class="n">PyCFunction</span><span class="p">)</span> <span class="n">_pyoscode_solve</span><span class="p">,</span> <span class="n">METH_VARARGS</span> <span class="o">|</span> <span class="n">METH_KEYWORDS</span><span class="p">,</span>
    <span class="n">solve_docstring</span><span class="p">},</span>
    <span class="p">{</span><span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">}</span>
<span class="p">};</span>

<span class="cp">#ifdef PYTHON3
</span><span class="k">static</span> <span class="k">struct</span> <span class="nc">PyModuleDef</span> <span class="n">_pyoscodemodule</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">PyModuleDef_HEAD_INIT</span><span class="p">,</span>
    <span class="s">"_pyoscode"</span><span class="p">,</span>
    <span class="n">module_docstring</span><span class="p">,</span>
    <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
    <span class="n">module_methods</span>
<span class="p">};</span>
<span class="cp">#endif
</span></code></pre></div></div> <p>Apart from including the necessary modules (<code class="language-plaintext highlighter-rouge">_python.hpp</code> for knowing the Python version, <code class="language-plaintext highlighter-rouge">&lt;array&gt;</code> for arrays, etc), here we define a short docstring for the module and for the methods in the module and store them in <code class="language-plaintext highlighter-rouge">static char</code>s.</p> <p>We then have to declare all functions that we’ll be able to call in the pyoscode module. The name of these functions, conventionally, is <code class="language-plaintext highlighter-rouge">_&lt;module name&gt;_&lt;method name&gt;</code>, in an attempt to imitate namespaces. The functions take and return <code class="language-plaintext highlighter-rouge">PyObject</code> types, which refer to all Python types, from <code class="language-plaintext highlighter-rouge">int</code>s to classes. The first <code class="language-plaintext highlighter-rouge">PyObject</code> argument, <code class="language-plaintext highlighter-rouge">self</code>, points the module itself, <code class="language-plaintext highlighter-rouge">args</code> is a tuple of positional arguments and <code class="language-plaintext highlighter-rouge">kwargs</code> are the keywords arguments to <code class="language-plaintext highlighter-rouge">solve()</code>.</p> <p>We still have to specify the relationship between the module and its methods we declared. As explained in <a href="https://dfm.io/posts/python-c-extensions/">Dan’s blogpost</a>, for more methods, one needs to add more lines like the one following the <code class="language-plaintext highlighter-rouge">static PyMethodDef ...</code>, which links the Python calls to the C functions by listing:</p> <ol> <li>the name of the function as it would be called from Python,</li> <li>the C function to link to (together with its type),</li> <li>the type of arguments the method will take: in our case positional and keyword arguments.</li> </ol> <p>In Python 3.x, there is one additional definition to be made, which is of the module definition struct holding all information needed to create a module object. The first element is always <code class="language-plaintext highlighter-rouge">PyModuleDef_HEAD_INIT</code>, the second is the name of the module, then the docstring of the module, -1, and finally a pointer to a table of module-level functions, described by <code class="language-plaintext highlighter-rouge">PyMethodDef</code> values (or <code class="language-plaintext highlighter-rouge">NULL</code>, if there are none). The mysterious “-1” expresses the fact that the module state will be kept in globals, and not in a per-module memory area. The latter would be useful if one used multiple sub-interpreters, e.g. when one wanted to allow more than one thread to be executing at a given time.</p> <p>We shall now define the methods we declared in <code class="language-plaintext highlighter-rouge">_pyoscode.hpp</code>. This definition will be the wrapper of the C/C++ function <code class="language-plaintext highlighter-rouge">solve()</code>, and so will be less general and reuseable than the structure of the code above. However, the beginnings (includes, and the definition of the initializing function of the module) of <code class="language-plaintext highlighter-rouge">_pyoscode.cpp</code> are still quite general.</p> <h3 id="_pyoscodecpp"><code class="language-plaintext highlighter-rouge">_pyoscode.cpp</code></h3> <p><code class="language-plaintext highlighter-rouge">_pyoscode.cpp</code> starts with a bunch of includes and definitions:</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define PY_SSIZE_T_CLEAN
#include</span> <span class="cpf">"_python.hpp"</span><span class="cp">
#include</span> <span class="cpf">"_pyoscode.hpp
#include "</span><span class="c1">system.hpp"</span><span class="cp">
#include</span> <span class="cpf">"solver.hpp"</span><span class="cp">
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include</span> <span class="cpf">&lt;numpy/arrayobject.h&gt;</span><span class="cp">
</span></code></pre></div></div> <p>In the above, <code class="language-plaintext highlighter-rouge">PY_SSIZE_T_CLEAN</code> is a macro that needs to be defined before including <code class="language-plaintext highlighter-rouge">Python.h</code> (which we’ll do later). When passing sized objects (e.g. arrays), the variable type storing the length of the object is either <code class="language-plaintext highlighter-rouge">int</code>, or <code class="language-plaintext highlighter-rouge">Py_ssize_t</code> if the macro has been defined. In the future, Python will only support the latter type, so we define <code class="language-plaintext highlighter-rouge">PY_SSIZE_T_CLEAN</code> for safety.</p> <p>The macro definition is followed by including the header files we’ll use in this file, then the definition <code class="language-plaintext highlighter-rouge">NPY_NO_DEPRECATED_API</code>, followed by the version of numpy API the project uses (1.7). This definition ensures that the developers have a grace period before changes in the numpy-C API break the code. When the developer updates the <code class="language-plaintext highlighter-rouge">NPY_1_7_API_VERSION</code> to <code class="language-plaintext highlighter-rouge">NP_1_8_API_VERSION</code>, and they find that some functionality has been deprecated, they can rest assured that their past releases will continue to work and they have time to fix the problems. So in summary, the <code class="language-plaintext highlighter-rouge">NPY_API_VERSION</code> should be set to the highest numpy version that’s been tested.</p> <p>Finally, we include headers from numpy we intend to use, in this case the <code class="language-plaintext highlighter-rouge">arrayobject.h</code>, since we’ll be using arrays.</p> <p>We then define the function that initializes the module, which has a special form that’s different between Python 2 and 3, so we make use of the <code class="language-plaintext highlighter-rouge">PYTHON3</code> flag set earlier:</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifdef PYTHON3
</span><span class="n">PyMODINIT_FUNC</span> <span class="nf">PyInit__pyoscode</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="n">import_array</span><span class="p">();</span>
    <span class="k">return</span> <span class="n">PyModule_Create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">_pyoscodemodule</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">#else
</span><span class="n">PyMODINIT_FUNC</span> <span class="nf">init_pyoscode</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">m</span> <span class="o">=</span> <span class="n">Py_InitModule3</span><span class="p">(</span><span class="s">"_pyoscode"</span><span class="p">,</span> <span class="n">module_methods</span><span class="p">,</span> <span class="n">module_docstring</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="n">m</span><span class="o">==</span><span class="nb">NULL</span><span class="p">)</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="n">import_array</span><span class="p">();</span>
<span class="p">}</span>
<span class="cp">#endif
</span></code></pre></div></div> <p>After initializing the module, we can start wrapping the module’s methods (<code class="language-plaintext highlighter-rouge">pyoscode.solve()</code>), which by convention we’ll call <code class="language-plaintext highlighter-rouge">_pyoscode_solve</code>.</p> <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">PyObject</span> <span class="o">*</span><span class="nf">_pyoscode_solve</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">kwargs</span><span class="p">){</span>

    <span class="kt">int</span> <span class="n">islogw</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">islogg</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">order</span><span class="o">=</span><span class="mi">3</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">full_output</span><span class="o">=</span><span class="s">""</span><span class="p">;</span>
    <span class="kt">double</span> <span class="n">ti</span><span class="p">,</span><span class="n">tf</span><span class="p">,</span><span class="n">rtol</span><span class="p">,</span><span class="n">atol</span><span class="p">,</span><span class="n">h0</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">x0</span><span class="p">,</span><span class="n">dx0</span><span class="p">;</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">tsobj</span><span class="p">,</span> <span class="o">*</span><span class="n">wsobj</span><span class="p">,</span> <span class="o">*</span><span class="n">gsobj</span><span class="p">;</span>
    <span class="c1">// Define keywords</span>
    <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">kwlist</span><span class="p">[]</span> <span class="o">=</span>
    <span class="p">{</span><span class="s">"ts"</span><span class="p">,</span> <span class="s">"ws"</span><span class="p">,</span> <span class="s">"gs"</span><span class="p">,</span> <span class="s">"ti"</span><span class="p">,</span> <span class="s">"tf"</span><span class="p">,</span> <span class="s">"x0"</span><span class="p">,</span> <span class="s">"dx0"</span><span class="p">,</span> <span class="s">"logw"</span><span class="p">,</span> <span class="s">"logg"</span><span class="p">,</span> <span class="s">"order"</span><span class="p">,</span> <span class="s">"rtol"</span><span class="p">,</span>
    <span class="s">"atol"</span><span class="p">,</span> <span class="s">"h"</span><span class="p">,</span> <span class="s">"full_output"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">};</span>

    <span class="c1">// Interpret input arguments.</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PyArg_ParseTupleAndKeywords</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="s">"OOOddDD|iiiddds"</span><span class="p">,</span>
        <span class="k">const_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">**&gt;</span><span class="p">(</span><span class="n">kwlist</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">tsobj</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">wsobj</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">gsobj</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ti</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">x0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dx0</span><span class="p">,</span>
        <span class="o">&amp;</span><span class="n">islogw</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">islogg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">order</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rtol</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">atol</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">h0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">full_output</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="c1">// Interpret input objects as numpy arrays</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">tsarray</span> <span class="o">=</span> <span class="n">PyArray_FROM_OTF</span><span class="p">(</span><span class="n">tsobj</span><span class="p">,</span> <span class="n">NPY_DOUBLE</span><span class="p">,</span> <span class="n">NPY_ARRAY_IN_ARRAY</span><span class="p">);</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">wsarray</span> <span class="o">=</span> <span class="n">PyArray_FROM_OTF</span><span class="p">(</span><span class="n">wsobj</span><span class="p">,</span> <span class="n">NPY_CDOUBLE</span><span class="p">,</span> <span class="n">NPY_ARRAY_IN_ARRAY</span><span class="p">);</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">gsarray</span> <span class="o">=</span> <span class="n">PyArray_FROM_OTF</span><span class="p">(</span><span class="n">gsobj</span><span class="p">,</span> <span class="n">NPY_CDOUBLE</span><span class="p">,</span> <span class="n">NPY_ARRAY_IN_ARRAY</span><span class="p">);</span>
    <span class="c1">// If that didn't work, throw an exception</span>
    <span class="k">if</span><span class="p">(</span><span class="n">tsarray</span><span class="o">==</span><span class="nb">NULL</span> <span class="n">or</span> <span class="n">wsarray</span><span class="o">==</span><span class="nb">NULL</span> <span class="n">or</span> <span class="n">gsarray</span><span class="o">==</span><span class="nb">NULL</span><span class="p">){</span>
        <span class="n">Py_XDECREF</span><span class="p">(</span><span class="n">tsarray</span><span class="p">);</span>    
        <span class="n">Py_XDECREF</span><span class="p">(</span><span class="n">wsarray</span><span class="p">);</span>    
        <span class="n">Py_XDECREF</span><span class="p">(</span><span class="n">gsarray</span><span class="p">);</span>    
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Get pointers to the data as c++-types</span>
    <span class="n">PyArrayObject</span> <span class="o">*</span><span class="n">tsarray_arr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">PyArrayObject</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">tsarray</span><span class="p">);</span>
    <span class="n">PyArrayObject</span> <span class="o">*</span><span class="n">wsarray_arr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">PyArrayObject</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">wsarray</span><span class="p">);</span>
    <span class="n">PyArrayObject</span> <span class="o">*</span><span class="n">gsarray_arr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">PyArrayObject</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">gsarray</span><span class="p">);</span>
    <span class="kt">double</span> <span class="o">*</span><span class="n">ts</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="o">*</span><span class="p">)</span><span class="n">PyArray_DATA</span><span class="p">(</span><span class="n">tsarray_arr</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="o">*</span><span class="n">ws</span> <span class="o">=</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;*</span><span class="p">)</span><span class="n">PyArray_DATA</span><span class="p">(</span><span class="n">wsarray_arr</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="o">*</span><span class="n">gs</span> <span class="o">=</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;*</span><span class="p">)</span><span class="n">PyArray_DATA</span><span class="p">(</span><span class="n">gsarray_arr</span><span class="p">);</span>
    
    <span class="c1">// Call the C++ functions to construct system and solve</span>
    <span class="n">de_system</span> <span class="n">sys</span> <span class="o">=</span> <span class="n">de_system</span><span class="p">(</span><span class="n">ts</span><span class="p">,</span><span class="n">ws</span><span class="p">,</span><span class="n">gs</span><span class="p">,</span><span class="n">islogw</span><span class="p">,</span><span class="n">islogg</span><span class="p">);</span>
    <span class="n">Solution</span> <span class="nf">solution</span><span class="p">(</span><span class="n">sys</span><span class="p">,</span><span class="n">x0</span><span class="p">,</span><span class="n">dx0</span><span class="p">,</span><span class="n">ti</span><span class="p">,</span><span class="n">tf</span><span class="p">,</span><span class="n">order</span><span class="p">,</span><span class="n">rtol</span><span class="p">,</span><span class="n">atol</span><span class="p">,</span><span class="n">h0</span><span class="p">,</span><span class="n">full_output</span><span class="p">);</span>
    <span class="n">solution</span><span class="p">.</span><span class="n">solve</span><span class="p">();</span>
    
    <span class="c1">// Build output values</span>
    <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">complex</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&gt;</span> <span class="n">sol</span><span class="p">;</span>
    <span class="n">sol</span> <span class="o">=</span> <span class="n">solution</span><span class="p">.</span><span class="n">sol</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">Nsol</span> <span class="o">=</span> <span class="n">sol</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="n">PyObject</span> <span class="o">*</span><span class="n">pysol</span><span class="o">=</span><span class="n">PyList_New</span><span class="p">(</span><span class="n">Nsol</span><span class="p">),</span><span class="o">*</span><span class="n">retdict</span><span class="p">;</span>
    <span class="n">Nsol</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="k">auto</span> <span class="n">it</span><span class="o">=</span><span class="n">sol</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span> <span class="n">it</span><span class="o">!=</span><span class="n">sol</span><span class="p">.</span><span class="n">end</span><span class="p">();</span> <span class="o">++</span><span class="n">it</span><span class="p">){</span>
        <span class="n">Py_complex</span> <span class="n">x_complex</span><span class="p">;</span>
        <span class="n">x_complex</span><span class="p">.</span><span class="n">real</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">real</span><span class="p">(</span><span class="o">*</span><span class="n">it</span><span class="p">);</span>
        <span class="n">x_complex</span><span class="p">.</span><span class="n">imag</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">imag</span><span class="p">(</span><span class="o">*</span><span class="n">it</span><span class="p">);</span>
        <span class="n">PyList_SetItem</span><span class="p">(</span><span class="n">pysol</span><span class="p">,</span><span class="n">Nsol</span><span class="p">,</span><span class="n">Py_BuildValue</span><span class="p">(</span><span class="s">"D"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">x_complex</span><span class="p">));</span> 
        <span class="o">++</span><span class="n">Nsol</span><span class="p">;</span>
    <span class="p">};</span>
    <span class="n">retdict</span> <span class="o">=</span> <span class="n">Py_BuildValue</span><span class="p">(</span><span class="s">"{s:O}"</span><span class="p">,</span><span class="s">"sol"</span><span class="p">,</span><span class="n">pysol</span><span class="p">);</span>

    <span class="c1">// Clean up</span>
    <span class="n">Py_DECREF</span><span class="p">(</span><span class="n">tsarray</span><span class="p">);</span>
    <span class="n">Py_DECREF</span><span class="p">(</span><span class="n">wsarray</span><span class="p">);</span>
    <span class="n">Py_DECREF</span><span class="p">(</span><span class="n">gsarray</span><span class="p">);</span>
    <span class="n">Py_DECREF</span><span class="p">(</span><span class="n">pysol</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">retdict</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div> <p>There’s a lot going on here, so let’s go through it one-by-one:</p> <ul> <li>The function takes in variables exactly as declared in <code class="language-plaintext highlighter-rouge">_pyoscode.hpp</code>.</li> <li>We then declare (and define) some local C-type variables to store values of variables that we’ll input to the C function <code class="language-plaintext highlighter-rouge">solve()</code> we’re wrapping.</li> <li>The C function <code class="language-plaintext highlighter-rouge">solve()</code> will take in three arrays, <code class="language-plaintext highlighter-rouge">ts, ws, gs</code>. We’ll take whatever Python sequence the user provides (<code class="language-plaintext highlighter-rouge">tsobj, wsobj, gsobj</code>), and convert them to well-behaved numpy arrays that C understands (<code class="language-plaintext highlighter-rouge">tsarray, wsarray, gsarray</code>), then extract the data from these into C arrays (<code class="language-plaintext highlighter-rouge">tsarray_arr, wsarray_arr, gsarray_arr</code>). We’ll talk about each of the conversions when we get to the relevant line.</li> <li>We then define the list of keyword arguments accepted by our C function <code class="language-plaintext highlighter-rouge">solver()</code>.</li> <li>The next line is how the API parses arguments given to Python functions, via <em>format strings</em> into local variables. There are three such functions depending on what type of arguments you’re expecting, here we use the type that will accept both positional and keyword. The <a href="https://docs.python.org/3/c-api/arg.html">relevant documentation</a> tells you that these format strings are strings of <em>format units</em>, telling C the type of argument to expect. We first pass the args and kwargs from the input of the wrapper, then the format units of the positional and keyword arguments separated by <code class="language-plaintext highlighter-rouge">|</code>. In the format string, <code class="language-plaintext highlighter-rouge">O</code> is any Python object which this function will assign <em>without conversion</em> to a C object pointer. <code class="language-plaintext highlighter-rouge">d</code> is for double: this converts a Python float to a C double. <code class="language-plaintext highlighter-rouge">D</code> will convert a Python complex to a C complex double. The keyword arguments are mainly <code class="language-plaintext highlighter-rouge">i</code> - integers, and there is also an <code class="language-plaintext highlighter-rouge">s</code> - string. We finally pass all the local variables these input variables will be assigned to, starting with the keyword argument list, then references to the other variables in the same order as they appear in the format string.</li> <li>The next line is the first set of array conversions: we use <code class="language-plaintext highlighter-rouge">PyArray_FROM_OTF</code> to convert the user-provided Python sequence into a well-behaved numpy array, with the second and third argument defining what type the array elements must be and any requirements the array must satisfy, respectively. If this conversion is unsuccessful, we’ll have to manually decrease the reference to the objects before returning, because this function always increases the reference count. To safeguard against decreasing the reference count of a <code class="language-plaintext highlighter-rouge">NULL</code>, we use <code class="language-plaintext highlighter-rouge">Py_XDECREF</code>, which carries out a check first.</li> <li>we now want to get pointers to the data stored in these numpy arrays. We do that by calling <code class="language-plaintext highlighter-rouge">PyArray_DATA</code>, but this expects a <code class="language-plaintext highlighter-rouge">PyArrayObject</code>, whereas we have generic <code class="language-plaintext highlighter-rouge">PyObject</code>s, so we have to perform a <code class="language-plaintext highlighter-rouge">reinterpret_cast</code> first. The end of all this is we have pointers to arrays, which we can now pass on to our C++ code!</li> <li>We do just that in the next few lines - pass the information on to the ODE solver, and let it work.</li> <li>Now it’s time to build the return values of our Python function. We’ll be returning a dictionary, with some descriptive keys pointing to lists that contain the solution of the ODE, its derivative, etc. evaluated at a set of time points (which may be internally determined by the solver, or by the user). For simplicity, let’s say we’ll only have one key-value pair in this dictionary, the solution of the ODE.</li> <li>The ODE solver returned the solution in the form of a complex-valued list attributed to a <code class="language-plaintext highlighter-rouge">Solution</code> object. So we first declare and define this as a C++ list, and get its size. We also declare the target Python list (note: this creates a new reference which we’ll have to destroy before returning!), and the dictionary to be returned.</li> <li>We iterate over the elements of this list, and build a C structure <code class="language-plaintext highlighter-rouge">Py_complex</code> from the real and imaginary parts of the C++ complex number. We then build a Python complex number (<code class="language-plaintext highlighter-rouge">D</code> is the format string encoding this) from the <code class="language-plaintext highlighter-rouge">Py_complex</code>, and set it as the current element of the Python list using <code class="language-plaintext highlighter-rouge">PyList_SetItem</code>.</li> <li>We build the dictionary to return: using the same format string logic, we declare that the dictionary maps Python strings to generic Python obejects, <code class="language-plaintext highlighter-rouge">{s:O}</code>, and identify the <code class="language-plaintext highlighter-rouge">O</code> as the Python list we just built.</li> <li>Finally, we decrease the reference counts of any remaining Python objects.</li> </ul> <p>We’re nearly done. All there’s left to do is to write the Python module’s <code class="language-plaintext highlighter-rouge">__init__.py</code> that defines its member functions as they would be called from Python, and then build the module. Since we only have one function, <code class="language-plaintext highlighter-rouge">solve()</code>, it is rather simple and is mostly docstring. We must not forget to import, however, the module we just wrote, <code class="language-plaintext highlighter-rouge">_pyoscode</code> :).</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">sys</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">_pyoscode</span>
<span class="kn">import</span> <span class="n">numpy</span>

<span class="k">def</span> <span class="nf">solve</span><span class="p">(</span><span class="n">ts</span><span class="p">,</span> <span class="n">ws</span><span class="p">,</span> <span class="n">gs</span><span class="p">,</span> <span class="n">ti</span><span class="p">,</span> <span class="n">tf</span><span class="p">,</span> <span class="n">x0</span><span class="p">,</span> <span class="n">dx0</span><span class="p">,</span> <span class="n">logw</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">logg</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">order</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
<span class="n">rtol</span><span class="o">=</span><span class="mf">1e-4</span><span class="p">,</span> <span class="n">atol</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">full_output</span><span class="o">=</span><span class="sh">""</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Solve a differential equation with the RKWKB method.
    
    Parameters
    ----------
    ts: numpy.ndarray [float] or list [float]
       An array of real numbers representing the values of the independe
       nt variable at which the frequency and friction term are evaluated. 
</span><span class="gp">    ...</span> 

<span class="s">    Returns
    -------
    A dictionary with the following keywords and values:
        sol: list [complex]
            A list containing the solution evaluated at timepoints listed under
            the </span><span class="sh">'</span><span class="s">t</span><span class="sh">'</span><span class="s"> keyword.
     
    </span><span class="sh">"""</span>
    <span class="c1"># Set direction of integration if initial stepsize, h, not given
</span>    <span class="k">if</span> <span class="n">h</span><span class="o">==</span><span class="bp">None</span><span class="p">:</span>
        <span class="n">h</span> <span class="o">=</span> <span class="n">numpy</span><span class="p">.</span><span class="nf">sign</span><span class="p">(</span><span class="n">tf</span> <span class="o">-</span> <span class="n">ti</span><span class="p">)</span>
        <span class="c1"># Handle the case of ti = tf
</span>        <span class="k">if</span> <span class="n">h</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
            <span class="n">h</span><span class="o">=</span><span class="mi">1</span>
    
    <span class="c1"># Run oscode from module library
</span>    <span class="n">resdict</span> <span class="o">=</span> <span class="n">_pyoscode</span><span class="p">.</span><span class="nf">solve</span><span class="p">(</span><span class="n">ts</span><span class="p">,</span> <span class="n">ws</span><span class="p">,</span> <span class="n">gs</span><span class="p">,</span> <span class="n">ti</span><span class="p">,</span> <span class="n">tf</span><span class="p">,</span> <span class="n">x0</span><span class="p">,</span> <span class="n">dx0</span><span class="p">,</span> <span class="n">logw</span><span class="o">=</span><span class="n">logw</span><span class="p">,</span> <span class="n">logg</span><span class="o">=</span><span class="n">logg</span><span class="p">,</span>
    <span class="n">order</span><span class="o">=</span><span class="n">order</span><span class="p">,</span> <span class="n">rtol</span><span class="o">=</span><span class="n">rtol</span><span class="p">,</span> <span class="n">atol</span><span class="o">=</span><span class="n">atol</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="n">h</span><span class="p">,</span> <span class="n">full_output</span><span class="o">=</span><span class="n">full_output</span><span class="p">)</span> 
    
    <span class="k">return</span> <span class="n">resdict</span>
</code></pre></div></div> <h2 id="the-build-script">The build script</h2> <p>The build script that compiles everything is the <code class="language-plaintext highlighter-rouge">setup.py</code> (or <code class="language-plaintext highlighter-rouge">setup.cfg</code>, but with entirely different syntax), which is placed at the top of the directory tree. Let’s see it:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kn">from</span> <span class="n">__future__</span> <span class="kn">import</span> <span class="n">absolute_import</span><span class="p">,</span> <span class="n">with_statement</span><span class="p">,</span> <span class="n">print_function</span><span class="p">,</span> <span class="n">division</span>
<span class="kn">from</span> <span class="n">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">Extension</span><span class="p">,</span> <span class="n">find_packages</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="n">pyoscode_module</span> <span class="o">=</span> <span class="nc">Extension</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">_pyoscode</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">sources</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">pyoscode/_pyoscode.cpp</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">include_dirs</span><span class="o">=</span><span class="p">[</span><span class="sh">'</span><span class="s">include</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">pyoscode</span><span class="sh">'</span><span class="p">,</span><span class="n">np</span><span class="p">.</span><span class="nf">get_include</span><span class="p">()],</span>
    <span class="n">depends</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">pyoscode/_python.hpp</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">pyoscode/_pyoscode.hpp</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">extra_compile_args</span><span class="o">=</span><span class="p">[</span><span class="sh">'</span><span class="s">-std=c++11</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">-Wall</span><span class="sh">'</span><span class="p">]</span>
    <span class="p">)</span>

<span class="nf">setup</span><span class="p">(</span>
    <span class="n">name</span><span class="o">=</span><span class="sh">"</span><span class="s">pyoscode</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">version</span><span class="o">=</span><span class="sh">"</span><span class="s">1.0.0</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">packages</span><span class="o">=</span><span class="nf">find_packages</span><span class="p">(),</span>
    <span class="n">install_requires</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">numpy</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">scipy</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">extras_require</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">examples:</span><span class="sh">"</span><span class="p">:[</span><span class="sh">"</span><span class="s">matplotlib</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">scipy</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">jupyter</span><span class="sh">"</span><span class="p">],</span>
    <span class="sh">"</span><span class="s">docs</span><span class="sh">"</span><span class="p">:[</span><span class="sh">"</span><span class="s">sphinx</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">sphinx-rtd-theme</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">numpydoc</span><span class="sh">"</span><span class="p">],</span> <span class="sh">"</span><span class="s">testing</span><span class="sh">"</span><span class="p">:[</span><span class="sh">"</span><span class="s">pytest</span><span class="sh">"</span><span class="p">]},</span>
    <span class="n">setup_requires</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">pytest-runner</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">numpy</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">tests_require</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">pytest</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">numpy</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">scipy</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">include_package_data</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">ext_modules</span><span class="o">=</span><span class="p">[</span><span class="n">pyoscode_module</span><span class="p">]</span>
<span class="p">)</span>

</code></pre></div></div> <p>First, we seem to be importing a module named <code class="language-plaintext highlighter-rouge">__future__</code>. We aren’t actually importing a module - this is called a future statement. It’s a compiler directive which causes the module to be compiled using syntax/semantics that will be available in a future release of Python, to ease migration to future versions. It’s also useful for when someone tries to import this module from Python 2.7, where words like <code class="language-plaintext highlighter-rouge">print</code>, or the division operator had different meanings than in 3.x.</p> <p><code class="language-plaintext highlighter-rouge">setuptools</code> is the library we use to package this Python project. We declare that we have a C/C++ extension module by constructing an <code class="language-plaintext highlighter-rouge">Extension</code> class, and giving it the following keyword arguments:</p> <ul> <li><code class="language-plaintext highlighter-rouge">name</code>: the full name of the extension (don’t forget the underscore),</li> <li><code class="language-plaintext highlighter-rouge">sources</code>: list of source filenames relative to where <code class="language-plaintext highlighter-rouge">setup.py</code> lives,</li> <li><code class="language-plaintext highlighter-rouge">include_dirs</code>: list of directories to search for C/C++ header files. The headers our C++ code uses are in <code class="language-plaintext highlighter-rouge">include/</code>, the headers for the wrapper itself are in <code class="language-plaintext highlighter-rouge">pyoscode/</code>, and since we included a numpy header, we need to also include the directory where numpy headers live, which is done automatically by <code class="language-plaintext highlighter-rouge">np.get_include()</code>,</li> <li><code class="language-plaintext highlighter-rouge">depends</code>: a list of files the extension depends on,</li> <li><code class="language-plaintext highlighter-rouge">extra_compile_args</code>: any additional platform- or compiler-specific flags. I just specify which version of the C++ standard library to use, and also tell the compiler to display warnings.</li> </ul> <p>The basic do-everything function is <code class="language-plaintext highlighter-rouge">setup()</code>. We now call this with many arguments (I’ve only included a few, but check out oscode’s repo for the complete set):</p> <ul> <li><code class="language-plaintext highlighter-rouge">name</code>: the name of the package - this is what you’ll <code class="language-plaintext highlighter-rouge">import</code>!</li> <li><code class="language-plaintext highlighter-rouge">version</code>: you guessed it, the version number,</li> <li><code class="language-plaintext highlighter-rouge">packages</code>: the list of packages to be included in the distribution package. Instead of typing everything out, you can just use <code class="language-plaintext highlighter-rouge">find_packages</code>, which without any arguments will just list all packages in <code class="language-plaintext highlighter-rouge">.</code>,</li> <li><code class="language-plaintext highlighter-rouge">install_requires</code>, <code class="language-plaintext highlighter-rouge">extras_require</code>, <code class="language-plaintext highlighter-rouge">setup_requires</code>, <code class="language-plaintext highlighter-rouge">tests_require</code>: these all specify dependencies. <code class="language-plaintext highlighter-rouge">install_requires</code> specifies any other distributions necessary for <em>core functionality</em> that will be installed when the present package is. <code class="language-plaintext highlighter-rouge">extras_require</code> on the other hand lists dependencies of extras, like that of examples, in a dictionary. <code class="language-plaintext highlighter-rouge">setup_requires</code> lists packages required for the setup script to run, and <code class="language-plaintext highlighter-rouge">tests_require</code> does the same for any tests. An important <em>caveat</em>: say your <code class="language-plaintext highlighter-rouge">setup.py</code> imports numpy before the <code class="language-plaintext highlighter-rouge">setup()</code> function is called. This sort of <em>build dependency</em> can only be ensured to be present before the setup script is ran by putting it in a <code class="language-plaintext highlighter-rouge">pyproject.toml</code> file. Again, this is important when distributing your package, not when building it in-place.</li> <li><code class="language-plaintext highlighter-rouge">include_package_data</code>: when True, upon distributing the package, <code class="language-plaintext highlighter-rouge">setuptools</code> automatically includes any data files in the package directories that are specified in a file called <code class="language-plaintext highlighter-rouge">MANIFEST.in</code>. This is necessary when e.g. you want to distribute your package via PyPI, i.e. you want people to just be able to <code class="language-plaintext highlighter-rouge">pip install</code> it. The <code class="language-plaintext highlighter-rouge">MANIFEST.in</code> makes sure all necessary files are included in the source distribution. When the package is built in-place however, this isn’t required (the files are already there).</li> <li><code class="language-plaintext highlighter-rouge">ext_modules</code>: <code class="language-plaintext highlighter-rouge">Extension</code> class instances to include, i.e. our <code class="language-plaintext highlighter-rouge">pyoscode_module</code>.</li> </ul> <p>And that’s it! To build your package in-place, just run</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install</span> <span class="nb">.</span>
</code></pre></div></div> <p>or</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python setup.py <span class="nb">install</span>
</code></pre></div></div> <h2 id="summary">Summary</h2> <p>Together with <a href="https://dfm.io/posts/python-c-extensions/">this</a> blogpost, and oscode’s <a href="https://github.com/fruzsinaagocs/oscode">repository</a>, this should be enough of a guide to help you write C/C++ extension in Python.</p> <h2 id="what-i-havent-talked-about">What I haven’t talked about</h2> <p>Future post(s) will discuss the following in more detail:</p> <ul> <li>distributing your package with PyPI (<code class="language-plaintext highlighter-rouge">pip</code>)</li> <li>what to do if you have external C/C++ dependencies 😱</li> <li>writing unit tests and continuous integration with Travis CI</li> <li>open-source licenses and open-source code review</li> </ul> <p>This theme supports rendering beautiful math in inline and display modes using <a href="https://www.mathjax.org/">MathJax 3</a> engine. You just need to surround your math expression with <code class="language-plaintext highlighter-rouge">$$</code>, like <code class="language-plaintext highlighter-rouge">$$ E = mc^2 $$</code>. If you leave it inside a paragraph, it will produce an inline expression, just like \(E = mc^2\).</p> <p>To use display mode, again surround your expression with <code class="language-plaintext highlighter-rouge">$$</code> and place it as a separate paragraph. Here is an example:</p> \[\sum_{k=1}^\infty |\langle x, e_k \rangle|^2 \leq \|x\|^2\] <p>You can also use <code class="language-plaintext highlighter-rouge">\begin{equation}...\end{equation}</code> instead of <code class="language-plaintext highlighter-rouge">$$</code> for display mode math. MathJax will automatically number equations:</p> <p>\begin{equation} \label{eq:cauchy-schwarz} \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) \end{equation}</p> <p>and by adding <code class="language-plaintext highlighter-rouge">\label{...}</code> inside the equation environment, we can now refer to the equation using <code class="language-plaintext highlighter-rouge">\eqref</code>.</p> <p>Note that MathJax 3 is <a href="https://docs.mathjax.org/en/latest/upgrading/whats-new-3.0.html">a major re-write of MathJax</a> that brought a significant improvement to the loading and rendering speed.</p>]]></content><author><name></name></author><category term="coding"/><category term="Python,"/><category term="C++,"/><category term="software"/><summary type="html"><![CDATA[A tutorial on how to wrap C++ code in Python]]></summary></entry></feed>