Lightweight Levenberg–Marquardt fitting library with CPU and CUDA-ready GPU codepaths that uses forward difference method to approximate gradients which avoids having to define analytical derivatives by hand.
This repository contains a fitting library (CPU + GPU sources), Python binding wiring, as well as examples that demonstrate fitting a 1D Gaussian and a log-norm correlation function on the CPU (and a GPU example is still in progress).
- CPU example: working and stable (see
examples/CPU/). - GPU fitting: work-in-progress — GPU kernels and estimators are present in
src/gpu/but the integration is still unstable. - Still tring to figure out the best approach for the ModelDescriptor (simple struct combining model definition and its derivative) adoptation in CUDA and how to avoid manual model definitions.
The project uses CMake. The repository was developed using the Ninja generator on Windows, but any CMake generator supported by your platform should work.
From the repository root (PowerShell):
mkdir build; cd build
cmake --build .After building, the CPU example executable Gauss_1D is available under the build output tree in examples/CPU (path depends on your CMake generator). From the build directory you can usually run:
.\examples\CPU\Gauss_1D.exeinclude/— public headers for models, types and utilities used by examples and library code.src/— implementation sources and bindings:src/cpu/— CPU implementation of the Levenberg–Marquardt solver and helper code (used by the CPU example).src/gpu/— CUDA.cu/.cuhsources with GPU kernels and estimators (WIP).src/binding/— binding code that hooks the native library to higher-level language bindings (eg. pybind11-based Python bindings).
examples/— example programs showing how to use the library:examples/CPU/— working CPU examples (Gauss_1D.cpp/.py&Correlation_function_LogNorm.cpp/.py).examples/GPU/— GPU example sources (requires a working CUDA toolchain and the GPU integration to be stable).
extern/pybind11/— vendoredpybind11sources used to expose C++ code to Python.package/— Python packaging helpers (minimalcpu_lmfitpackage andsetup.py) After compiling the library.dllfiles are copied here automatically and allows direct installation withpip.tests/— experimental test sources and smoke tests.docs/— documentation, logs and notes.
The top-level CMakeLists.txt (project root) configures the project and performs these main actions:
- Sets minimum CMake version and declares the project with languages
CXXandCUDA. - Configures the C++ standard to C++11.
- Adds common include directories used by the project (
include/,include/cpu,include/gpu, and amakefolder used for helper headers). - Adds subdirectories that produce the library and examples:
src/cpu— builds the CPU solver library and/or objects.src/gpu— builds CUDA sources (only if a CUDA toolchain is present).src/binding— builds language bindings (pybind11 wiring present inextern/).examples/CPUandexamples/GPU— example executables.
The effect is that the top-level CMake lists manages all subprojects and ensures headers and sources are visible to the examples.
Makefile(top-level) is a small convenience wrapper around the canonical CMake workflow. It exposes a few common recipes (targets) that configure, build and clean various out-of-source build directories so developers can usemakesemantics instead of calling CMake directly.
Makefile targets and what they do
-
default/build:- The default target. When you run
makewith no arguments it invokesbuild. - Orchestrates building CPU, GPU and example targets by invoking
build-cpu,build-gpuandbuild-examplesin sequence.
- The default target. When you run
-
configure:- Prints a short OS detection message. It does not run CMake configuration steps by itself; it is useful to quickly confirm whether the Makefile detected Windows (
Windows_NT) or not.
- Prints a short OS detection message. It does not run CMake configuration steps by itself; it is useful to quickly confirm whether the Makefile detected Windows (
-
show-config:- Prints the
CMAKE_BUILDandCMAKE_CONFIGUREvariables as seen by the Makefile. Note: those variables are not set elsewhere in the Makefile, so this target may print empty values unless you exported them yourself.
- Prints the
-
build-cpu:- Configures and builds CPU-only targets into a CPU-specific out-of-source build directory.
- On Windows this uses the Visual Studio generator and a build directory named
build-msvc-cpu(generator: "Visual Studio 17 2022" with x64 arch). - On Unix-like systems it sets
CC=gcc CXX=g++and configures intobuild-cpu. - The recipe runs the configure step then invokes
cmake --buildfor the chosen directory and configuration (Release).
-
build-gpu:- Configures and builds GPU targets into a GPU-specific out-of-source build directory.
- On Windows it uses
build-msvc-gpuwith the Visual Studio generator, and on Unix-like systems it usesbuild-gpu. - Rely on CMake to detect the CUDA toolchain (nvcc) when configuring the GPU build.
-
build-examples:- Configures and builds the example targets (for instance the
Gauss_1Dexample) intobuild-examples. - This target uses the default generator and is useful when you just want the example binaries and not full CPU/GPU libraries separately.
- Configures and builds the example targets (for instance the
-
rebuild:- Convenience target that runs
cleanfollowed bybuildto produce a fresh build directory and artifacts.
- Convenience target that runs
-
clean:- Removes build directories and a few package-related build artefacts. The recipe deletes
build-cpu,build-gpu,build-examples,build-msvc-cpu,build-msvc-gpuand somepackage/cpu_lmfitbuild outputs. - Implementation uses POSIX-style shell commands (
[ -d ... ],rm -rf,shopt) so behavior on native Windowscmd.exemay vary; it generally works in Unix-like environments or MSYS/MinGW shells that provide a POSIX shell.
- Removes build directories and a few package-related build artefacts. The recipe deletes
-
run:- Runs the example executable
Gauss_1Dfrom thebuild-examplesoutput tree (./build-examples/Gauss_1D.exe). - Depending on your generator and platform (single-config vs multi-config generators such as Visual Studio) the actual executable may live in a configuration-specific subdirectory (e.g.
Release\orDebug\) or a different path; adjust the path if needed.
- Runs the example executable
Notes and platform caveats
- The Makefile contains
ifeq ($(OS),Windows_NT)branching to select generator and build directories for Windows vs Unix-like systems. However, some recipes rely on a POSIX shell — so usingmakefrom a Unixlike environment on Windows (MSYS2, Git Bash, WSL) yields the smoothest behavior. - The Makefile is intentionally minimal: it simply drives
cmake -S/-Bandcmake --buildwith a small set of well-chosen directories. Advanced or custom build workflows should invoke CMake directly for full control.
- The CPU example (
examples/CPU/Gauss_1D.*) demonstrates using the CPU solver to fit a 1D Gaussian and is the best starting point for understanding how to use the library. - The
src/binding/directory together with the vendoredextern/pybind11/provide the pieces needed to build Python bindings. If you plan to build Python wheels or import the library from Python, inspectsrc/binding/CMakeLists.txtandpackage/.
- If you only need the CPU example, configure CMake without CUDA or build only the
src/cpusubproject and theexamples/CPUtarget. - To build GPU code you need a CUDA-capable system and a compatible CUDA toolkit; the CMake setup will try to enable CUDA language support when available. (For this I would first need to finally figure out the best approach for easy/user-friendly model initialization with low overhead 🙃)
- Use an out-of-source build directory (for example
build/) to keep build artefacts separate from source.
- Finally finish GPU integration and provide a GPU example binary.
- Close all the TODO's
- Add unit tests and CI for cross-platform builds.
-
src/cpu/lm_solver.cpp:
- "WIP: Calculation of parameter_stddevs_ not yet implemented" — implement computation and storage of parameter standard deviations returned by the solver when transitioning to CUDA and cuBLAS (disregarded in the CPU version since CPU verison uses a "home-made" solver with Cholesky decomposition)
- "WIP: Optimize by batch processing - compute all gradients at once rather than per parameter" — improve Jacobian computation performance (batch numerical differences or vectorized approach, also disregarded in the CPU verion sicne the main focus was on CUDA based approach).
- "TODO: Add a report message if solver fails" — provide richer error reporting when normal-equation solve or decomposition fails.
-
src/binding/binding_cpu.cpp:
- "TODO: Add a binding to model functions that need speedup" — expose selected, performance-critical model helper functions directly to Python to avoid trampoline overhead.
-
include/cpu/lm_solver.hpp:
- "TODO: and their standard deviations" — public API currently contains a commented form of
copy_optimized_paramsthat would also return parameter stddevs; reinstate and wire it once stddevs are computed in the CUDA verison with cuBLAS.
- "TODO: and their standard deviations" — public API currently contains a commented form of
-
include/cpu/forward_difference.hpp:
- "TODO: Tune the step size for better accuracy and stability." — empirically select or compute a better step-size rule for numeric differentiation.
See the LICENSE file in the repository root.