devito: a DSL that compiles symbolic stencils into parallel C
DSL and compiler framework for automated finite-differences and stencil computation
At a glance
- What is it?
- Devito is a Python package that implements optimised stencil computation from high-level symbolic problem definitions, and it does so by building on SymPy and then generating and just-in-time compiling code for CPUs, GPUs and clusters. A four-line diffusion example is the whole pitch, and the interesting engineering is in what happens between that expression and the loop nest you get.
- Who is it for?
- Adopt Devito if you are writing finite difference or stencil kernels and want the loop structure, the parallelisation and the memory layout decided by a compiler rather than by hand, since that is the entire reason the project exists. Do not adopt it for a one-off numerical routine, because the compilation step and the symbolic overhead will cost more than a hand-written NumPy kernel.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Four lines of Python that compile to a parallel loop nest
Devito is a Python package to implement optimized stencil computation, for example finite differences, image processing and machine learning, from high-level symbolic problem definitions. It builds on SymPy and employs automated code generation and just-in-time compilation to execute optimized computational kernels on several computer platforms, including CPUs, GPUs and clusters.
The example that sells it is a 2D diffusion operator, and it is genuinely four lines:
>>> grid = Grid(shape=(10, 10))
>>> f = TimeFunction(name='f', grid=grid, space_order=2)
>>> eqn = Eq(f.dt, 0.5 * f.laplace)
>>> op = Operator(Eq(f.forward, solve(eqn, f.forward)))What is happening in that snippet is the whole idea. Grid creates the discretised domain, TimeFunction creates a function of time on that grid with a second order spatial discretisation, and f.laplace is not a function call you implement, it is a symbolic expression the compiler derives from the grid and the order. The Eq then holds the time derivative equal to that expression, solve turns it into an explicit update rule, and Operator turns the whole thing into compiled code.
The execution is equally short. An Operator generates low-level code from an ordered collection of Eq, the example being for a single equation, and the generated code may also be compiled and executed:
>>> op(t=timesteps, dt=dt)The important claim is the one after that. There is virtually no limit to the complexity of an Operator, because the Devito compiler will automatically analyze the input, detect and apply optimizations, including single- and multi-node parallelism, and eventually generate code with suitable loops and expressions.
So the difference from hand-writing a solver is that you state the mathematics and the compiler decides the data layout, the loop order, the vectorisation and the parallel decomposition.
The feature list, and which of those features are the hard part
The key features list is long, but the entries fall into a few groups, and the shape of the list tells you where the project's effort goes.
The language side comes first. A functional language to express finite difference operators, straightforward mechanisms to adjust the discretisation, constructs to express sparse operators such as interpolation, classic linear operators such as convolutions, and tensor contractions. Then first class support for boundary conditions and adjoint operators, and a flexible API to define custom stencils, sub-domains, sub-sampling and staggered grids.
The adjoint support is worth pausing on, because it is the feature that makes Devito useful for inverse problems and full waveform inversion, which the repository topics name as fwi and rtm. The idea of an adjoint operator is that you want the transpose of the forward operator, applied to a residual, and for a hand-written stencil that is a separate and error-prone derivation. Devito derives it from the same symbolic definition.
The code generation side is where the list gets specific. Generation of highly optimized parallel code, with SIMD vectorization, CPU and GPU parallelism via OpenMP and OpenACC, multi-node parallelism via MPI, blocking, and aggressive symbolic transformations for FLOP reduction. Distributed NumPy arrays over multi-node domain decompositions. Inspection and customization of the generated code. An autotuning framework to ease performance tuning.
And then the integration story. Smooth integration with popular Python packages such as NumPy, SymPy, Dask and SciPy, as well as machine learning frameworks such as TensorFlow and PyTorch.
That last line is the most strategically interesting, because a JIT compiler that emits C is straightforward, and one that plays well with the rest of the scientific Python stack is not. Dask in particular implies that the domain decomposition is expressed in a way that can be distributed rather than only parallelised on one node.
The Docker path, and the two images you get
The easiest way to try Devito is through Docker, and the installation is a clone and a compose command:
# get the code
git clone https://github.com/devitocodes/devito.git
cd devito
# start a jupyter notebook server on port 8888
docker-compose up devitoAfter running the last command the terminal will display a URL such as https://127.0.0.1:8888/?token=XXX, which you paste into a browser to start a Jupyter notebook session where you can go through the tutorials provided with Devito or create your own notebooks.
That is a good default for a compiler project, because the first thing you want to do with a code generator is look at what it generated, and a notebook with the source next to the output is the right way to do that.
The compose file defines two services from the same Dockerfile, docker/Dockerfile.devito, and the second one is the interesting one. The devito service mounts the source, the tests and the examples into the container, so a change on your machine is a change in the container with no rebuild. The devito.nvidia service builds from the same Dockerfile with a different base argument, devitocodes/bases:nvidia-nvc, and sets the nvidia runtime, so the GPU path is a separate service rather than a flag.
Both services publish port 8888 and use the same entrypoint, docker-entrypoint.sh, and both mount the three directories the same way, so the difference between them is exactly the toolchain, which is the right way to keep the two environments comparable.
The container is also the mechanism for the other backends, since the compose file mounts the source rather than copying it, so the MPI and CPU code paths can be exercised in the same container layout.
Dependencies, Python versions, and the packaging choices
The requirements file shows the dependency set, and it is a short one for a project that compiles code.
pip between 9.0.1 and 26.2, numpy between 1.26 and 2.4.3, sympy between 1.12.1 and 1.15, psutil, py-cpuinfo, cgen, codepy, multidict, anytree and packaging.
Three of those are the interesting ones. sympy is the symbolic core, which is why the README describes Devito as building on SymPy rather than merely using it. cgen and codepy are the C generation and C parsing libraries, and their presence tells you that the code generation path is a C emitter plus a C parser, which is how Devito can both emit and then reason about the code it produced. psutil and py-cpuinfo are there for the autotuning framework and for platform detection.
The Python floor is a range, not a point: requires-python is >=3.10,<3.15, and the classifiers list 3.10 through 3.14. The classifiers also say Development Status 5 - Production/Stable, and the licence is MIT with a license file.
The build backend is setuptools with setuptools-scm, and the version and the dependencies are declared dynamic, which is how the version tracks the git tag rather than being written into a file by hand. The authors are Imperial College London with two maintainers at devitocodes.com, and the topic classifiers are worth noting because they tell you the intended audience: scientific and engineering mathematics and physics, and software development code generators and compilers.
The optional dependencies are split into separate requirement files, requirements-mpi.txt, requirements-nvidia.txt, requirements-optional.txt and requirements-testing.txt, which is the standard way to keep a JIT compiler installable on a laptop while still supporting the cluster and GPU backends.
The test matrix, the examples, and the benchmark story
The badges at the top of the README show three separate build workflows, and they describe the backends the project actually supports.
There is a build status for the core backend without MPI, a build status with MPI, and a build status on GPU, plus a code coverage badge and an asv benchmarked badge. Three test jobs for three execution targets is the minimum for a project whose whole value is that the same specification compiles correctly to a serial CPU loop, a GPU kernel and a distributed MPI program.
The examples directory is organised by domain, and the list is a good map of who uses the project: cfd for computational fluid dynamics, seismic for seismology, which is where the full waveform inversion work lives, checkpointing for saving and restoring state, compiler for the code generation internals, finance for a completely different application of the same machinery, mpi for distributed runs, performance for tuning, timestepping for the time integration, userapi for the public interface, and misc.
The finance example is the interesting one, because it shows the framework is not limited to physics. A financial pricing calculation is a stencil computation like any other, and the same symbolic machinery applies.
On performance, the README is unusually specific about who should read what. If you are interested in generation of parallel code for CPU, GPU or multi-node via MPI, in performance tuning, or in benchmarking operators, then you should take a look at the benchmarks/user README. The project publishes its own benchmark results at devitocodes.github.io/devito-performance, hosted by asv, which is the airspeed velocity benchmarking framework.
Publishing your own benchmarks is a rarer courtesy than it should be, and it matters more for a JIT compiler than for an ordinary library, because the compiler's output changes with every release and the only way to know whether a change helped is to have a record of what it measured before.
Where Devito sits, and what the project is really for
The homepage is devitoproject.org, the package is on PyPI as devito, there is a Docker image on Docker Hub marked important, and there is a Binder link that serves the tutorial notebooks without a local install.
The project came out of Imperial College London, which explains the domain emphasis. The topics list names finite-difference, gpu, hpc, jit, rtm, stencil, fwi, ultrasound-imaging and code-generation. Ultrasound imaging and full waveform inversion are the applications where a hand-written solver is genuinely hard, because they need both forward and adjoint operators over the same discretisation and a memory layout that a GPU can actually chew through.
So the honest summary is that Devito is a compiler with a scientific Python front end, built by people who needed to write a seismic solver once and did not want to write the loop nest, the boundary conditions and the adjoint twice. The functional language and the symbolic discretisation exist so that a solver can be written as mathematics, and everything else in the feature list is the machinery for turning that into something that runs at speed on the machine you have.
The maintenance picture is healthy. The last push to the repository was on 2026-10-20, the repository is not archived, and the release list shows v4.8.23 from 2026-08-07, v4.8.22 from 2026-06-22 and v4.8.21 from 2026-02-23. The patch releases every couple of months suggest a codebase that is stable in its interfaces and evolving in its backends, which is the right shape for a project whose users write specifications against it.
The project also asks for citations, with a CITATION.md in the repository, and there is a code of conduct, a contributing guide, a FAQ file, and a pre-commit configuration. The Slack channel is where the maintainers say they would like to hear from users.
Editorial conclusion
Adopt Devito if you are writing finite difference or stencil kernels and want the loop structure, the parallelisation and the memory layout decided by a compiler rather than by hand, since that is the entire reason the project exists. Do not adopt it for a one-off numerical routine, because the compilation step and the symbolic overhead will cost more than a hand-written NumPy kernel. Before you start, check your Python version against the supported range of 3.10 to 3.14, and read the generated code rather than trusting it, because inspection and customisation of the output is a documented feature.
Frequently asked questions
What is Devito and what does it generate?
It is a Python package that implements optimized stencil computation from high-level symbolic definitions, building on SymPy and using automated code generation and just-in-time compilation to produce optimised kernels for CPUs, GPUs and clusters. An Operator generates low-level code from an ordered collection of Eq, and that code may also be compiled and executed.
How do I try Devito quickly?
Clone the repository, cd into it and run docker-compose up devito, which starts a Jupyter notebook server on port 8888. The terminal prints a URL with a token that you open in a browser to reach the tutorials.
Which Python versions does Devito support?
requires-python is >=3.10,<3.15, and the classifiers list Python 3.10 through 3.14. The runtime dependencies include sympy between 1.12.1 and 1.15, numpy between 1.26 and 2.4.3, and cgen and codepy for C generation and parsing.
What parallel backends does Devito target?
The generated code covers SIMD vectorization, CPU and GPU parallelism via OpenMP and OpenACC, and multi-node parallelism via MPI, with blocking and aggressive symbolic transformations for FLOP reduction. There is also an autotuning framework and distributed NumPy arrays over multi-node domain decompositions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/devitocodes-devito)Was this useful?