diff --git a/content/accel-cython.rst b/content/accel-cython.rst new file mode 100644 index 0000000..a5aabbc --- /dev/null +++ b/content/accel-cython.rst @@ -0,0 +1,380 @@ +Cython +------ + +Cython is a superset of Python that additionally supports calling C functions and declaring C types on variables and class attributes. +It is also a versatile, general purpose compiler. +Since it is supports a superset of Python syntax, nearly all Python code, including 3rd party Python packages are also valid Cython code. +Under Cython, source code gets translated into optimized C/C++ code and compiled as Python extension modules. + +Developers can either: + +- prototype and develop Python code in IPython/Jupyter using the ``%%cython`` magic command (**easy**), or +- run the ``cython`` command-line utility to produce a ``.c`` file from a ``.py`` or ``.pyx`` file, + which in turn needs to be compiled with a C compiler to an ``.so`` library, which can then be directly imported in a Python program (**intermediate**), or +- use setuptools_ or meson_ with meson-python_ to automate the aforementioned build process (**advanced**). + +.. _setuptools: https://setuptools.pypa.io/en/latest/userguide/ext_modules.html +.. _meson: https://mesonbuild.com/Cython.html +.. _meson-python: https://mesonbuild.com/meson-python/index.html + +Herein, we restrict the discussion to the Jupyter-way of using the ``%%cython`` magic. +A full overview of Cython capabilities refers to the `documentation `_. + +.. important:: + + Due to a `known issue`_ with ``%%cython -a`` in ``jupyter-lab`` we have to use the ``jupyter-nbclassic`` interface + for this episode. + +.. _known issue: https://github.com/cython/cython/issues/7319 + +Python: Baseline (step 0) +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. demo:: Demo: Cython + + Consider a problem to integrate a function: + + .. math:: + I = \int^{b}_{a}(x^2 - x)dx + + which can be numerically approximated as the following sum: + + .. math:: + I \approx \delta x \sum_{i=0}^{N-1} (x_i^2 - x_i) + + where :math:`a \le x_i \lt b`, and all :math:`x_i` are uniformly spaced apart by :math:`\delta x = (b - a) / N`. + + **Objective**: Repeatedly compute the approximate integral for 1000 different combinations of + :math:`a`, :math:`b` and :math:`N`. + + +Python code is provided below: + +.. literalinclude:: example/integrate_python.py + +We generate a dataframe and apply the :meth:`apply_integrate_f` function on its columns, timing the execution: + +.. code-block:: ipython + + import pandas as pd + + df = pd.DataFrame( + { + "a": np.random.randn(1000), + "b": np.random.randn(1000), + "N": np.random.randint(low=100, high=1000, size=1000) + } + ) + + %timeit apply_integrate_f(df['a'], df['b'], df['N']) + # 101 ms ± 736 μs per loop (mean ± std. dev. of 7 runs, 10 loops each) + + +Cython: Benchmarking (step 1) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to use Cython, we need to import the Cython extension: + +.. code-block:: ipython + + %load_ext cython + +As a first cythonization step, we add the cython magic command (``%%cython -a``) on top of Jupyter code cell. +We start by a simply compiling the Python code using Cython without any changes. The code is shown below: + +.. literalinclude:: example/cython/integrate_cython_step1.py + + +.. figure:: img/cython_annotate.png + :width: 80% + :align: left + :alt: The Cython code above is displayed where various lines of the code are highlighted with yellow background colour of varying intensity. + + Annotated Cython code obtained by running the code above. + The yellow coloring in the output shows us the amount of pure Python code. + +Our task is to remove as much yellow as possible by *static typing*, *i.e.* explicitly declaring arguments, parameters, variables and functions. + +We benchmark the Python code just using Cython, and it may give either similar or a slight increase in performance. + +.. code-block:: ipython + + %timeit apply_integrate_f_cython_step1(df['a'], df['b'], df['N']) + # 102 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) + + +Cython: Adding data type annotation to input variables (step 2) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Now we can start adding data type annotation to the input variables as highlightbed in the code example/cython below: + +.. tabs:: + .. group-tab:: Pure Python + .. literalinclude:: example/cython/integrate_cython_step2_purepy.py + :emphasize-lines: 7,10,18-20 + + .. group-tab:: Cython + .. literalinclude:: example/cython/integrate_cython_step2.py + :emphasize-lines: 6,9,17-19 + +.. code-block:: ipython + + # this will not work + #%timeit apply_integrate_f_cython_step2(df['a'], df['b'], df['N']) + + # this command works (see the description below) + %timeit apply_integrate_f_cython_step2(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy()) + # 34.3 ms ± 537 μs per loop (mean ± std. dev. of 7 runs, 10 loops each) + +.. warning:: + + You can not pass a Series directly since Cython definition is specific to an array. + Instead we should use ``Series.to_numpy()`` to get the underlying NumPy array which works nicely with Cython. + +.. note:: + + Cython uses the normal C syntax for types and provides all standard ones, including pointers. + Here is a list of some primitive C data types (refer to Cython's documentation on :cython:ref:`types`): + + .. csv-table:: + :widths: auto + :delim: ; + + Cython type identifier; Pure Python dtype; + ``char``; ``cython.char`` + ``int``; ``cython.int`` + ``unsigned int``; ``cython.uint`` + ``long``; ``cython.long`` + ``float``; ``cython.float`` + ``double``; ``cython.double`` + ``double complex``; ``cython.doublecomplex`` + ``size_t``; ``cython.size_t`` + + Using these data types, we can also annotate arrays (see :cython:ref:`memoryviews`): + + - 1D ``np.float64`` array would be equivalent to ``cython.double[:]``, + - 2D ``np.float64`` array would be equivalent to ``cython.double[:, :]`` and so on... + +.. important:: + + to quote the :cython:ref:`Cython documentation `, + + **Typing is not a necessity** + + Providing static typing to parameters and variables is convenience to speed up your code, but it is not a necessity. Optimize where and when needed. In fact, + typing can slow down your code in the case where the typing does not allow optimizations but where Cython still needs to check that the type of some object matches the declared type. + + +Cython: Adding data type annotation to functions (step 3) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Next step, we further add type annotation to functions. There are three ways of declaring functions: + +- ``def`` - Python style: + + - Called by Python or Cython code, and both input/output are Python objects. + - Declaring argument types and local types (thus return values) can allow Cython to generate optimized code which speeds up the execution. + - Once types are declared, a ``TypeError`` will be raised if the function is passed with the wrong types. + +- ``@cython.cfunc`` or ``cdef`` - C style: + + - :cython:ref:`cdef ` functions are called from Cython and C, but not from Python code. + - Cython treats functions as pure C functions, which can take any type of arguments, including non-Python types, `e.g.`, pointers. + - This usually gives the *best performance*. + - However, one should really take care of the functions declared by ``cdef`` as these functions are actually writing in C. + +- ``@cython.ccall`` or ``cpdef`` - C/Python mixed style: + + - :cython:ref:`cpdef ` function combines both ``cdef`` and ``def``. + - Cython will generate a ``cdef`` function for C types and a ``def`` function for Python types. + - In terms of performance, ``cpdef`` functions may be *as fast as* those using ``cdef`` and might be as slow as ``def`` declared functions. + +.. tabs:: + .. group-tab:: Pure Python + .. literalinclude:: example/cython/integrate_cython_step3_purepy.py + :emphasize-lines: 7,11,20 + + .. group-tab:: Cython + .. literalinclude:: example/cython/integrate_cython_step3.py + :emphasize-lines: 6,9,16 + +.. code-block:: ipython + + %timeit apply_integrate_f_cython_step3(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy()) + # 29.2 ms ± 152 μs per loop (mean ± std. dev. of 7 runs, 10 loops each) + + +Cython: Adding data type annotation to local variables and return (step 4) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Last step, we can add type annotation to local variables within functions and the return value. + +.. tabs:: + .. group-tab:: Pure Python + .. literalinclude:: example/cython/integrate_cython_step4_purepy.py + :emphasize-lines: 7,15-18,32-35 + + .. group-tab:: Cython + .. literalinclude:: example/cython/integrate_cython_step4.py + :emphasize-lines: 6,9-11,19,24-25 + +.. code-block:: ipython + + %timeit apply_integrate_f_cython_step4(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy()) + # 471 μs ± 7.38 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) + + +Now it is ~200 times faster than the baseline Python implementation, and all we have done is to add type declarations on the Python code! + +.. figure:: img/cython_annotate_2.png + :width: 80% + :align: left + + We indeed see much less Python interaction in the code from step 1 to step 4. + +Other useful features +^^^^^^^^^^^^^^^^^^^^^ + +There are some useful (and possibly advanced) features which are not covered in this episode. Some of +these features are called :cython:ref:`magic attributes `. Here are a few: + +- ``cython.cimports`` package for importing and calling C libraries such as :cython:ref:`libc.math`. + +.. note:: + + Differences between ``import`` (for Python) and ``cimport`` (for Cython) statements + + - ``import`` gives access to Python libraries, functions or attributes + - ``cimport`` gives access to C libraries, functions or attributes + + In case of Numpy it is common to use the following, and Cython will internally handle this ambiguity. + + .. tabs:: + .. group-tab:: Pure Python + .. code-block:: python + + from cython.cimports.libc.stdlib import malloc, free # Allocate and free memory + from cython.cimports.libc import math # For math functions like sin, cos etc. + from cython.cimports import numpy as np # access to NumPy C API + + .. group-tab:: Cython + .. code-block:: cython + + from libc.stdlib cimport malloc, free + from libc.libc cimport math + cimport numpy as np + + +- ``cython.nogil``, which can act both as a decorator or context-manager, to manage the GIL (Global Interpreter Lock). + See :cython:ref:`cython_and_gil`. + +- ``@cython.boundscheck(False)`` and ``@cython.wraparound(False)`` decorators to tune indexing of Numpy array. + See :cython:ref:`numpy_tutorial`. + +- ``@cython.cclass`` to declare :cython:ref:`extension-types` which behave similar to Python classes. + +In addition to the above Cython can also, + +- :cython:ref:`augment with .pxd files ` where the Python code is kept as it is and the ``.pxd`` file + describes the type annotation. In this form ``.pxd`` is very similar in function to a C/C++ header file + or ``.pyi`` Python type annnotation file, + +- create parallel code using :cython:ref:`parallel-block` and ``prange`` iterator for element-wise parallel operation or reductions + based on OpenMP threads (see :cython:ref:`parallel-tutorial`). + + +.. demo:: + + Here is a code which showcases most of the features above, except the ``@cython.cclass`` feature and the use of ``.pxd`` files. + + .. tabs:: + .. group-tab:: Pure Python + .. code-block:: python + :emphasize-lines: 2-3,5-6,12-13 + + import cython + from cython.parallel import parallel, prange + from cython.cimports.libc.math import sqrt + + @cython.boundscheck(False) + @cython.wraparound(False) + def normalize(x: cython.double[:]): + """Normalize a 1D array by dividing all its elements using its root-mean-square (RMS) value.""" + i: cython.Py_ssize_t + total: cython.double = 0 + norm: cython.double + with cython.nogil, parallel(): + for i in prange(x.shape[0]): + total += x[i]*x[i] + norm = sqrt(total) + for i in prange(x.shape[0]): + x[i] /= norm + + .. group-tab:: Cython + .. code-block:: cython + :emphasize-lines: 2-3,5-6,12-13 + + cimport cython + from cython.parallel cimport parallel, prange + from libc.math cimport sqrt + + @cython.boundscheck(False) + @cython.wraparound(False) + def normalize(double[:] x): + """Normalize a 1D array by dividing all its elements using its root-mean-square (RMS) value.""" + cdef Py_ssize_t i + cdef double total = 0 + cdef double norm + with nogil, parallel(): + for i in prange(x.shape[0]): + total += x[i]*x[i] + norm = sqrt(total) + for i in prange(x.shape[0]): + x[i] /= norm + + .. group-tab:: Numpy + .. code-block:: python + + def normalize_numpy(x): + total = np.dot(x, x) + norm = total ** 0.5 + + x[:] /= norm + + .. group-tab:: Naive Python implementation + .. code-block:: python + + from math import sqrt + + def normalize_naive(x): + total = 0 + for i in range(x.shape[0]): + total += x[i] * x[i] + + norm = sqrt(total) + for i in range(x.shape[0]): + x[i] /= norm + + .. note:: + + If you compare performance of the the Cython code versus the Numpy code, you might observe that it is either on-par, or slightly worse than Numpy. + This is because Numpy vectorized operations also makes use of OpenMP parallelism and is heavily optimized. Nevertheless, it is orders of magnitude + better than a naive implementation. + +Conclusions +^^^^^^^^^^^ + +.. keypoints:: + + - Cython is a versatile, general purpose compiler for Python code + - Cython is a great way to write high-performance code in Python where algorithms are not available in scientific libraries like Numpy and Scipy and + require custom implementation + +.. seealso:: + + In order to make Cython code reusable often some packaging is necessary. The compilation to binary extension can either happen during the packaging itself, or + during installation of a Python package. To learn more about how to package such extensions, read the following guides: + + - *pyOpenSci Python packaging guide*'s page on `build tools `__ + - *Python packaging user guide*'s page on `packaging binary extensions `__ + diff --git a/content/conf.py b/content/conf.py index 7c33d39..5e8d051 100644 --- a/content/conf.py +++ b/content/conf.py @@ -144,4 +144,5 @@ # "instruct": ("https://enccs.github.io/instructor-training/", None), # "lesson": ("https://coderefinery.github.io/sphinx-lesson/", None), # "myst": ("https://myst-parser.readthedocs.io/en/latest/", None), -} + "cython": ("https://cython.readthedocs.io/en/stable", None), +} \ No newline at end of file diff --git a/content/example/cython/integrate_cython_all_steps.py b/content/example/cython/integrate_cython_all_steps.py new file mode 100644 index 0000000..0b70f92 --- /dev/null +++ b/content/example/cython/integrate_cython_all_steps.py @@ -0,0 +1,61 @@ +%%cython -a + +cimport cython +cimport numpy as np +import numpy as np + + +cdef double f_cython_dtype(double x): + return x * (x - 1) + + +cpdef double integrate_f_cython_dtype(double a, double b, int N): + cdef int i + cdef double s, dx + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_dtype(a + i * dx) + return s * dx + + +cpdef np.ndarray[double] apply_integrate_f_cython_dtype(np.ndarray[double] col_a, + np.ndarray[double] col_b, + np.ndarray[int] col_N): + cdef int i, n + n = len(col_N) + res = np.empty(n) + for i in range(n): + res[i] = integrate_f_cython_dtype(col_a[i], col_b[i], col_N[i]) + + return res + + +cpdef double[:] apply_integrate_f_cython_dtype2(double[:] col_a, + double[:] col_b, + int[:] col_N): + cdef int i, n + n = len(col_N) + res = np.empty(n) + for i in range(n): + res[i] = integrate_f_cython_dtype(col_a[i], col_b[i], col_N[i]) + + return res + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef double[:] apply_integrate_f_cython_dtype3(double[:] col_a, + double[:] col_b, + int[:] col_N): + cdef int i, n + n = len(col_N) + res = np.empty(n) + for i in range(n): + res[i] = integrate_f_cython_dtype(col_a[i], col_b[i], col_N[i]) + + return res + + +%timeit apply_integrate_f_cython_dtype(np.asarray(df['a']),np.asarray(df['b']),np.asarray(df['N'],dtype=np.int32)) + diff --git a/content/example/cython/integrate_cython_step1.py b/content/example/cython/integrate_cython_step1.py new file mode 100644 index 0000000..70a3458 --- /dev/null +++ b/content/example/cython/integrate_cython_step1.py @@ -0,0 +1,20 @@ +%%cython -a + +import numpy as np + +def f_cython_step1(x): + return x * (x - 1) + +def integrate_f_cython_step1(a, b, N): + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_step1(a + i * dx) + return s * dx + +def apply_integrate_f_cython_step1(col_a, col_b, col_N): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step1(col_a[i], col_b[i], col_N[i]) + return res diff --git a/content/example/cython/integrate_cython_step2.py b/content/example/cython/integrate_cython_step2.py new file mode 100644 index 0000000..c655830 --- /dev/null +++ b/content/example/cython/integrate_cython_step2.py @@ -0,0 +1,25 @@ +%%cython -a + +import numpy as np + + +def f_cython_step2(double x): + return x ** 2 - x + +def integrate_f_cython_step2(double a, double b, long N): + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_step2(a + i * dx) + return s * dx + +def apply_integrate_f_cython_step2( + double[:] col_a, + double[:] col_b, + long[:] col_N +): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step2(col_a[i], col_b[i], col_N[i]) + return res diff --git a/content/example/cython/integrate_cython_step2_purepy.py b/content/example/cython/integrate_cython_step2_purepy.py new file mode 100644 index 0000000..ad77c52 --- /dev/null +++ b/content/example/cython/integrate_cython_step2_purepy.py @@ -0,0 +1,26 @@ +%%cython -a + +import cython +import numpy as np + + +def f_cython_step2(x: cython.double): + return x ** 2 - x + +def integrate_f_cython_step2(a: cython.double, b: cython.double, N: cython.long): + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_step2(a + i * dx) + return s * dx + +def apply_integrate_f_cython_step2( + col_a: cython.double[:], + col_b: cython.double[:], + col_N: cython.long[:], +): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step2(col_a[i], col_b[i], col_N[i]) + return res \ No newline at end of file diff --git a/content/example/cython/integrate_cython_step3.py b/content/example/cython/integrate_cython_step3.py new file mode 100644 index 0000000..fa3b6a2 --- /dev/null +++ b/content/example/cython/integrate_cython_step3.py @@ -0,0 +1,25 @@ +%%cython -a + +import numpy as np + + +cdef f_cython_step3(double x): + return x ** 2 - x + +cdef integrate_f_cython_step3(double a, double b, long N): + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_step3(a + i * dx) + return s * dx + +cpdef apply_integrate_f_cython_step3( + double[:] col_a, + double[:] col_b, + long[:] col_N +): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step3(col_a[i], col_b[i], col_N[i]) + return res diff --git a/content/example/cython/integrate_cython_step3_purepy.py b/content/example/cython/integrate_cython_step3_purepy.py new file mode 100644 index 0000000..72b80ec --- /dev/null +++ b/content/example/cython/integrate_cython_step3_purepy.py @@ -0,0 +1,30 @@ +%%cython -a + +import cython +import numpy as np + + +@cython.cfunc +def f_cython_step3(x: cython.double): + return x ** 2 - x + +@cython.cfunc +def integrate_f_cython_step3(a: float, b: float, N: int): + s = 0 + dx = (b - a) / N + + for i in range(N): + s += f_cython_step3(a + i * dx) + return s * dx + +@cython.ccall +def apply_integrate_f_cython_step3( + col_a: cython.double[:], + col_b: cython.double[:], + col_N: cython.long[:] +): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step3(col_a[i], col_b[i], col_N[i]) + return res \ No newline at end of file diff --git a/content/example/cython/integrate_cython_step4.py b/content/example/cython/integrate_cython_step4.py new file mode 100644 index 0000000..eddb327 --- /dev/null +++ b/content/example/cython/integrate_cython_step4.py @@ -0,0 +1,31 @@ +%%cython -a + +import numpy as np + + +cdef double f_cython_step4(double x): + return x ** 2 - x + +cdef double integrate_f_cython_step4(double a, double b, long N): + cdef double s, dx + cdef long i + + s = 0 + dx = (b - a) / N + for i in range(N): + s += f_cython_step4(a + i * dx) + return s * dx + +cpdef double[:] apply_integrate_f_cython_step4( + double[:] col_a, + double[:] col_b, + long[:] col_N +): + cdef long n,i + cdef double[:] res + + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step4(col_a[i], col_b[i], col_N[i]) + return res diff --git a/content/example/cython/integrate_cython_step4_purepy.py b/content/example/cython/integrate_cython_step4_purepy.py new file mode 100644 index 0000000..a68a32e --- /dev/null +++ b/content/example/cython/integrate_cython_step4_purepy.py @@ -0,0 +1,41 @@ +%%cython -a + +import cython +import numpy as np + +@cython.cfunc +def f_cython_step4(x: cython.double) -> cython.double: + return x ** 2 - x + +@cython.cfunc +def integrate_f_cython_step4( + a: cython.double, + b: cython.double, + N: cython.long +) -> cython.double: + s: cython.double + dx: cython.double + i: cython.long + + s = 0 + dx = (b - a) / N + + for i in range(N): + s += f_cython_step4(a + i * dx) + return s * dx + +@cython.ccall +def apply_integrate_f_cython_step4( + col_a: cython.double[:], + col_b: cython.double[:], + col_N: cython.long[:] +) -> cython.double[:]: + n: cython.int + i: cython.int + res: cython.double[:] + + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f_cython_step4(col_a[i], col_b[i], col_N[i]) + return res \ No newline at end of file diff --git a/content/example/integrate_python.py b/content/example/integrate_python.py new file mode 100644 index 0000000..f6e8dad --- /dev/null +++ b/content/example/integrate_python.py @@ -0,0 +1,18 @@ +import numpy as np + +def f(x): + return x ** 2 - x + +def integrate_f(a, b, N): + s = 0 + dx = (b - a) / N + for i in range(N): + s += f(a + i * dx) + return s * dx + +def apply_integrate_f(col_a, col_b, col_N): + n = len(col_N) + res = np.empty(n,dtype=np.float64) + for i in range(n): + res[i] = integrate_f(col_a[i], col_b[i], col_N[i]) + return res diff --git a/content/img/cython_annotate.png b/content/img/cython_annotate.png new file mode 100644 index 0000000..e069eee Binary files /dev/null and b/content/img/cython_annotate.png differ diff --git a/content/img/cython_annotate_2.png b/content/img/cython_annotate_2.png new file mode 100644 index 0000000..bc2316b Binary files /dev/null and b/content/img/cython_annotate_2.png differ diff --git a/content/index.md b/content/index.md index 2e8332c..148d430 100644 --- a/content/index.md +++ b/content/index.md @@ -21,6 +21,7 @@ Intro :maxdepth: 1 episode.md +accel-cython ``` ```{toctree} diff --git a/notebooks/accel-cython.ipynb b/notebooks/accel-cython.ipynb new file mode 100644 index 0000000..3f88aae --- /dev/null +++ b/notebooks/accel-cython.ipynb @@ -0,0 +1,379 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a54e4bf0-a7ce-4a0e-8da7-510faa5cb704", + "metadata": {}, + "source": [ + "# Accelerating Python with Cython\n", + "\n", + "## Baseline (step 0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93a50c5c-3731-43a2-9505-cf0b49114174", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def f(x):\n", + " return x ** 2 - x\n", + "\n", + "def integrate_f(a, b, N):\n", + " s = 0\n", + " dx = (b - a) / N\n", + " for i in range(N):\n", + " s += f(a + i * dx)\n", + " return s * dx\n", + "\n", + "def apply_integrate_f(col_a, col_b, col_N):\n", + " n = len(col_N)\n", + " res = np.empty(n,dtype=np.float64)\n", + " for i in range(n):\n", + " res[i] = integrate_f(col_a[i], col_b[i], col_N[i])\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a20b9692-40e5-4277-bafe-1e6f70f07233", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.DataFrame(\n", + " {\n", + " \"a\": np.random.randn(1000),\n", + " \"b\": np.random.randn(1000),\n", + " \"N\": np.random.randint(low=100, high=1000, size=1000)\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa668e34-d5f1-4a08-83e5-9370c045ee1f", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit apply_integrate_f(df['a'], df['b'], df['N'])" + ] + }, + { + "cell_type": "markdown", + "id": "c6dc9d19-4251-4b5b-8f78-5e7d79fc5929", + "metadata": {}, + "source": [ + "## Cython: Benchmarking (step 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea06f471-16c8-4411-8b0e-a4a1f9deddc1", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext cython" + ] + }, + { + "cell_type": "markdown", + "id": "9012d800-081a-4e72-b4b1-96c0069c6fb6", + "metadata": {}, + "source": [ + "Run\n", + "```\n", + "%load ../content/example/cython/integrate_cython_step1.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baa8de99-7ff8-4dc4-81f9-a76f5413bcbd", + "metadata": {}, + "outputs": [], + "source": [ + "%load ../content/example/cython/integrate_cython_step1.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b021bca-7458-41c2-97ba-b4273b02a0c0", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit apply_integrate_f_cython_step1(df['a'], df['b'], df['N'])" + ] + }, + { + "cell_type": "markdown", + "id": "cc41a3a6-ed79-4ecc-a927-c3968cea2d05", + "metadata": {}, + "source": [ + "## Cython: Adding data type annotation to input variables (step 2)" + ] + }, + { + "cell_type": "markdown", + "id": "35928478-0f2a-4624-85c7-d46fc082d493", + "metadata": {}, + "source": [ + "Run either\n", + "```\n", + "%load ../content/example/cython/integrate_cython_step2.py\n", + "%load ../content/example/cython/integrate_cython_step2_purepy.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40536dd9-110f-4ef5-8f50-de4fe6f3cb51", + "metadata": {}, + "outputs": [], + "source": [ + "%load ../content/example/cython/integrate_cython_step2_purepy.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a572467f-ee57-4abe-8aae-4869827fe2be", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit apply_integrate_f_cython_step2(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy())" + ] + }, + { + "cell_type": "markdown", + "id": "3db10ce0-962f-47c4-b4a1-bc1a430da392", + "metadata": {}, + "source": [ + "## Cython: Adding data type annotation to functions (step 3)" + ] + }, + { + "cell_type": "markdown", + "id": "8cfaf04b-5512-44d9-94d0-3626ff81abf1", + "metadata": {}, + "source": [ + "Run either\n", + "```\n", + "%load ../content/example/cython/integrate_cython_step3.py\n", + "%load ../content/example/cython/integrate_cython_step3_purepy.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbb9b95c-6405-4742-8009-83e0f62a8fcb", + "metadata": {}, + "outputs": [], + "source": [ + "%load ../content/example/cython/integrate_cython_step3_purepy.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83c6cc45-5ec8-4e5a-b6f4-c66c07ca9574", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit apply_integrate_f_cython_step3(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy())" + ] + }, + { + "cell_type": "markdown", + "id": "ccf73ab1-0765-4b5f-a814-44a74d6076d9", + "metadata": {}, + "source": [ + "## Cython: Adding data type annotation to local variables (step 4)" + ] + }, + { + "cell_type": "markdown", + "id": "39f2ef1f-ac0e-4f4d-a069-e2acc6afceef", + "metadata": {}, + "source": [ + "Run either\n", + "```\n", + "%load ../content/example/cython/integrate_cython_step4.py\n", + "%load ../content/example/cython/integrate_cython_step4_purepy.py\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e93eb17b-0bcb-458d-b0ea-4645be1edd1f", + "metadata": {}, + "outputs": [], + "source": [ + "%load ../content/example/cython/integrate_cython_step4_purepy.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eb52127-9141-4243-a82b-f80e0355df06", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit apply_integrate_f_cython_step4(df['a'].to_numpy(), df['b'].to_numpy(), df['N'].to_numpy())" + ] + }, + { + "cell_type": "markdown", + "id": "9725be23-2741-4d6f-9b32-f87798f78d33", + "metadata": {}, + "source": [ + "## Demo: Other useful features" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba77b19b-6e84-4ef7-9b75-4d8bb568ec04", + "metadata": {}, + "outputs": [], + "source": [ + "%%cython\n", + "\n", + "import cython\n", + "from cython.parallel import parallel, prange\n", + "from cython.cimports.libc.math import sqrt\n", + "\n", + "@cython.boundscheck(False)\n", + "@cython.wraparound(False)\n", + "def normalize(x: cython.double[:]):\n", + " \"\"\"Normalize a 1D array by dividing all its elements using its root-mean-square (RMS) value.\"\"\"\n", + " i: cython.Py_ssize_t\n", + " total: cython.double = 0\n", + " norm: cython.double\n", + " with cython.nogil, parallel():\n", + " for i in prange(x.shape[0]):\n", + " total += x[i]*x[i]\n", + " norm = sqrt(total)\n", + " for i in prange(x.shape[0]):\n", + " x[i] /= norm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cf12cab-19da-44c3-a010-0fd5d7db2106", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def normalize_numpy(x):\n", + " total = np.dot(x, x)\n", + " norm = total ** 0.5\n", + "\n", + " x[:] /= norm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b696540-9277-48d6-a95a-44a54163bd6a", + "metadata": {}, + "outputs": [], + "source": [ + "from math import sqrt\n", + "\n", + "def normalize_naive(x):\n", + " total = 0\n", + " for i in range(x.shape[0]):\n", + " total += x[i] * x[i]\n", + "\n", + " norm = sqrt(total)\n", + " for i in range(x.shape[0]):\n", + " x[i] /= norm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1b47e5f-0257-47f5-aade-ca633b196e73", + "metadata": {}, + "outputs": [], + "source": [ + "a = 10\n", + "b = 100\n", + "xs = (b - a) * np.random.random_sample(size=100_000) + a\n", + "xs.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "215209c7-8189-4cbd-b226-ab2ad544ee93", + "metadata": {}, + "outputs": [], + "source": [ + "xs_copy = xs.copy()\n", + "%time normalize(xs_copy)\n", + "xs_copy.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2654dd72-3326-4a11-83d9-6aa92d5edf40", + "metadata": {}, + "outputs": [], + "source": [ + "xs_copy = xs.copy()\n", + "%time normalize_numpy(xs_copy)\n", + "xs_copy.mean()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bf6493d-e876-47a3-a952-d7d4a44c3dbf", + "metadata": {}, + "outputs": [], + "source": [ + "xs_copy = xs.copy()\n", + "%time normalize_naive(xs_copy)\n", + "xs_copy.mean()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pylock.toml b/pylock.toml index fa40ec9..ba40392 100644 --- a/pylock.toml +++ b/pylock.toml @@ -180,6 +180,28 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", upload-time = 2025-07-25T14:02:04Z, size = 6319, hashes = { sha256 = "2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971" } } wheels = [{ url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", upload-time = 2025-07-25T14:02:02Z, size = 7294, hashes = { sha256 = "c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417" } }] +[[packages]] +name = "cython" +version = "3.2.0" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/52/82/01f0b63287cb922e5ba96c5147c30f1e51f541ce91bd178025bb3518b1ba/cython-3.2.0.tar.gz", upload-time = 2025-11-05T13:35:04Z, size = 3267264, hashes = { sha256 = "41fdce8237baee2d961c292ed0386903dfe126f131e450a62de0fd7a5280d4b2" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/d0/dc4b260e8fde81b23ab4dca56948b3e69617ef470247ec6a3e09370a9849/cython-3.2.0-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-11-05T13:35:45Z, size = 2950437, hashes = { sha256 = "d900e58e826f9a5a27b0e2b50e33473e9986a5bae375c39b0f2e19f2c545fa23" } }, + { url = "https://files.pythonhosted.org/packages/c8/53/c322bf0486a938ad954a645866b67e978777d79183cf0a042bda6bea11de/cython-3.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-11-05T13:35:47Z, size = 3209331, hashes = { sha256 = "a9d38cd3aab720d21fa6d6ee168228352f69aea0a95bd4fb84e8879c6ed38fbb" } }, + { url = "https://files.pythonhosted.org/packages/cd/48/55d02dba0606768d3450afd088e2bbcd6f8a54977dce041c2c3c1894631c/cython-3.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-11-05T13:35:49Z, size = 3370974, hashes = { sha256 = "92b31d0b7b0a49b3d2aa94faaf75d44a03174cff2616b341a8853c919e511d51" } }, + { url = "https://files.pythonhosted.org/packages/ce/bd/6dab19652b68464572b7a137d07a91ebe86db2a81c35842ff5e49ef23403/cython-3.2.0-cp313-cp313-win_amd64.whl", upload-time = 2025-11-05T13:35:51Z, size = 2746274, hashes = { sha256 = "2847b74e76dbad612f6fc7182c12a5f78cffb0d05808fd2c4b638cf02d1aade6" } }, + { url = "https://files.pythonhosted.org/packages/dd/6b/9e1e171fe19274465d84dffa4610d46f434b1ae945e946802db396695d67/cython-3.2.0-cp39-abi3-macosx_10_9_x86_64.whl", upload-time = 2025-11-05T13:36:08Z, size = 2869249, hashes = { sha256 = "04821ce06598a3aa5c9e0270d98960cfe6556dedbd1418c65e4479162b8ae74a" } }, + { url = "https://files.pythonhosted.org/packages/c4/f1/f461726f664668a96072b2a245bdfae566d68e2eb1393ec72780cc59c21e/cython-3.2.0-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", upload-time = 2025-11-05T13:36:11Z, size = 3204332, hashes = { sha256 = "54b5b1c72a63da822b3f4739a0e31546c0a19f8e834b174906bf817ed5f9d65f" } }, + { url = "https://files.pythonhosted.org/packages/78/d8/73c07ce64cae496e5f5a6dfe3e53574af1a8ef777e2a834d10dae8b67a4e/cython-3.2.0-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", upload-time = 2025-11-05T13:36:13Z, size = 2851317, hashes = { sha256 = "6155a6c360e32af1aaa16fa10b0119b49deeadff42a1958973324150870af1b5" } }, + { url = "https://files.pythonhosted.org/packages/bc/d9/d9f321637b8034b5028fa5fe7d1085ffa9351fea350af6510d5cb924c014/cython-3.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", upload-time = 2025-11-05T13:36:15Z, size = 2987155, hashes = { sha256 = "861258ac3878b76c57b9b5a379787d772a0bc47fec9167b43986777de542c474" } }, + { url = "https://files.pythonhosted.org/packages/f8/b5/9f9e7d261f083b4066d734b27a7872b0c584fd4c3578196652dbf72b3f62/cython-3.2.0-cp39-abi3-musllinux_1_2_armv7l.whl", upload-time = 2025-11-05T13:36:17Z, size = 2884219, hashes = { sha256 = "85dbf955e3193893d0288105afa0fa5f4e835ff587061681f240a4f0487c44fb" } }, + { url = "https://files.pythonhosted.org/packages/88/64/5aeb6e43e0ded9efedc5a516f87a487fdca8e434491cc352e5a805380459/cython-3.2.0-cp39-abi3-musllinux_1_2_i686.whl", upload-time = 2025-11-05T13:36:19Z, size = 3218067, hashes = { sha256 = "3b3f13822526726bac43275c0e92916bbcc2c30e9f559edc4c1132670b70498d" } }, + { url = "https://files.pythonhosted.org/packages/c4/a0/1958f54cd79d8251a330b9c9652b2a5ceba6a3fcec10782dd03e2a23c74f/cython-3.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", upload-time = 2025-11-05T13:36:21Z, size = 3108277, hashes = { sha256 = "ab18d09673d219008be5b6174bcbb6dbfd50904e66371f104a8a4698b791472d" } }, + { url = "https://files.pythonhosted.org/packages/9c/84/9b8112160cab922b97edef00616ed18771567d88b5ba9d30d1736880c345/cython-3.2.0-cp39-abi3-win32.whl", upload-time = 2025-11-05T13:36:23Z, size = 2430852, hashes = { sha256 = "c9fd986413fc52929b916187630a9abab9f876299951488c4b905ad5346afee6" } }, + { url = "https://files.pythonhosted.org/packages/8f/57/65d3de140b51c45dd6892846bfabdfaaa032e2418f1cb1a2f46058c1fe42/cython-3.2.0-cp39-abi3-win_arm64.whl", upload-time = 2025-11-05T13:36:25Z, size = 2435793, hashes = { sha256 = "ee2ea79ddeb721f912e7efea039b9db059c81767ff04fbf9a995f64e1187df99" } }, + { url = "https://files.pythonhosted.org/packages/20/58/1f798ddb7fe6bfddf85f4f97d2d4ad63a491a7b643e85c1e274d0f09138e/cython-3.2.0-py3-none-any.whl", upload-time = 2025-11-05T13:35:00Z, size = 1252818, hashes = { sha256 = "73f7f4c75acde5b5b4df05b11fdc2705ec637b99241d1bc2f4ebf345f7a2ea90" } }, +] + [[packages]] name = "debugpy" version = "1.8.17" @@ -318,6 +340,13 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", upload-time = 2025-11-05T12:18:54Z, size = 4422115, hashes = { sha256 = "5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e" } } wheels = [{ url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", upload-time = 2025-11-05T12:18:52Z, size = 618911, hashes = { sha256 = "bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f" } }] +[[packages]] +name = "ipython-genutils" +version = "0.2.0" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz", upload-time = 2017-03-13T22:12:26Z, size = 22208, hashes = { sha256 = "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl", upload-time = 2017-03-13T22:12:25Z, size = 26343, hashes = { sha256 = "72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8" } }] + [[packages]] name = "ipython-pygments-lexers" version = "1.1.1" @@ -551,6 +580,13 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", upload-time = 2025-02-12T10:53:03Z, size = 93985, hashes = { sha256 = "5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4" } } wheels = [{ url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", upload-time = 2025-02-12T10:53:02Z, size = 84579, hashes = { sha256 = "9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d" } }] +[[packages]] +name = "nbclassic" +version = "1.3.3" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/ec/cc/a495b5eb9a964b70c6ae8c861168b78386d2520fd89c68390932f96400b2/nbclassic-1.3.3.tar.gz", upload-time = 2025-09-16T20:33:15Z, size = 64116062, hashes = { sha256 = "434228763f8cee754318cd6dfa42370db191af630dabab8e30bafc8c1aa3eee6" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/3d/fd/dfb6db427bb4e0a50c9802b11df0b69d9364192f3db999849cde9209c8d0/nbclassic-1.3.3-py3-none-any.whl", upload-time = 2025-09-16T20:33:08Z, size = 11527229, hashes = { sha256 = "dcee5149aa6aa01846c7458d6394b29b325213b5e118ee14c80d689122e0e4f2" } }] + [[packages]] name = "nbclient" version = "0.10.2" @@ -586,6 +622,36 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", upload-time = 2024-02-14T23:35:18Z, size = 13167, hashes = { sha256 = "b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb" } } wheels = [{ url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", upload-time = 2024-02-14T23:35:16Z, size = 13307, hashes = { sha256 = "411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef" } }] +[[packages]] +name = "numpy" +version = "2.3.4" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", upload-time = 2025-10-15T16:18:11Z, size = 20582187, hashes = { sha256 = "a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T16:16:10Z, size = 20950335, hashes = { sha256 = "c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966" } }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:16:12Z, size = 14179878, hashes = { sha256 = "a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3" } }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:16:14Z, size = 5108673, hashes = { sha256 = "3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197" } }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:16:16Z, size = 6641438, hashes = { sha256 = "043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e" } }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:16:18Z, size = 14281290, hashes = { sha256 = "4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7" } }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:16:21Z, size = 16636543, hashes = { sha256 = "fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953" } }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:16:23Z, size = 16056117, hashes = { sha256 = "40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37" } }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:16:27Z, size = 18577788, hashes = { sha256 = "ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd" } }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", upload-time = 2025-10-15T16:16:29Z, size = 6282620, hashes = { sha256 = "e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646" } }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", upload-time = 2025-10-15T16:16:31Z, size = 12784672, hashes = { sha256 = "56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d" } }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", upload-time = 2025-10-15T16:16:33Z, size = 10196702, hashes = { sha256 = "a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc" } }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-10-15T16:16:36Z, size = 21049003, hashes = { sha256 = "86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879" } }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-10-15T16:16:39Z, size = 14302980, hashes = { sha256 = "838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562" } }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", upload-time = 2025-10-15T16:16:41Z, size = 5231472, hashes = { sha256 = "d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a" } }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", upload-time = 2025-10-15T16:16:43Z, size = 6739342, hashes = { sha256 = "84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6" } }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-10-15T16:16:46Z, size = 14354338, hashes = { sha256 = "817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7" } }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-10-15T16:16:48Z, size = 16702392, hashes = { sha256 = "85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0" } }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-10-15T16:16:51Z, size = 16134998, hashes = { sha256 = "2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f" } }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-10-15T16:16:53Z, size = 18651574, hashes = { sha256 = "035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64" } }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", upload-time = 2025-10-15T16:16:55Z, size = 6413135, hashes = { sha256 = "fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb" } }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", upload-time = 2025-10-15T16:16:57Z, size = 12928582, hashes = { sha256 = "15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c" } }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", upload-time = 2025-10-15T16:17:00Z, size = 10266691, hashes = { sha256 = "b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40" } }, +] + [[packages]] name = "packaging" version = "25.0" @@ -593,6 +659,27 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", upload-time = 2025-04-19T11:48:59Z, size = 165727, hashes = { sha256 = "d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" } } wheels = [{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", upload-time = 2025-04-19T11:48:57Z, size = 66469, hashes = { sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484" } }] +[[packages]] +name = "pandas" +version = "2.3.3" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", upload-time = 2025-09-29T23:34:51Z, size = 4495223, hashes = { sha256 = "e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", upload-time = 2025-09-29T23:21:05Z, size = 11544671, hashes = { sha256 = "56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713" } }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", upload-time = 2025-09-29T23:21:15Z, size = 10680807, hashes = { sha256 = "bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8" } }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-29T23:21:27Z, size = 11709872, hashes = { sha256 = "e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d" } }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-29T23:21:40Z, size = 12306371, hashes = { sha256 = "318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac" } }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2025-09-29T23:21:55Z, size = 12765333, hashes = { sha256 = "4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c" } }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2025-09-29T23:22:10Z, size = 13418120, hashes = { sha256 = "93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493" } }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", upload-time = 2025-09-29T23:25:04Z, size = 10993991, hashes = { sha256 = "f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee" } }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", upload-time = 2025-09-29T23:22:24Z, size = 12048227, hashes = { sha256 = "75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5" } }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", upload-time = 2025-09-29T23:22:37Z, size = 11411056, hashes = { sha256 = "74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21" } }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2025-09-29T23:22:51Z, size = 11645189, hashes = { sha256 = "6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78" } }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2025-09-29T23:23:05Z, size = 12121912, hashes = { sha256 = "900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110" } }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", upload-time = 2025-09-29T23:23:28Z, size = 12712160, hashes = { sha256 = "a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86" } }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", upload-time = 2025-09-29T23:24:24Z, size = 13199233, hashes = { sha256 = "c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc" } }, +] + [[packages]] name = "pandocfilters" version = "1.5.1" @@ -714,6 +801,13 @@ index = "https://pypi.org/simple" sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", upload-time = 2025-10-06T04:15:18Z, size = 17683, hashes = { sha256 = "f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f" } } wheels = [{ url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", upload-time = 2025-10-06T04:15:17Z, size = 15548, hashes = { sha256 = "af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2" } }] +[[packages]] +name = "pytz" +version = "2025.2" +index = "https://pypi.org/simple" +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", upload-time = 2025-03-25T02:25:00Z, size = 320884, hashes = { sha256 = "360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", upload-time = 2025-03-25T02:24:58Z, size = 509225, hashes = { sha256 = "5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" } }] + [[packages]] name = "pywinpty" version = "3.0.2" diff --git a/pyproject.toml b/pyproject.toml index ca5f300..68443c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,14 @@ dev = [ { include-group = "notebook" }, "jupyterlab>=4.4.6", "jupyterlab-myst>=2.4.2", + "nbclassic>=1.3.3", ] # All dependencies used within Jupyter Notebooks -notebook = [] +notebook = [ + "cython>=3.2.0", + "numpy>=2.3.4", + "pandas>=2.3.3", +] [build-system]