Papers
Topics
Authors
Recent
Search
2000 character limit reached

CoDiPack: High-Performance Reverse-Mode AD

Updated 8 July 2026
  • CoDiPack is an open-source C++ library for reverse-mode AD that uses tape-based operator overloading with Jacobi taping and expression templates.
  • Its modular architecture enables integration of external derivative kernels and supports local adjoints for shared-memory parallel reverse mode.
  • The package offers advanced features such as native complex number support, preaccumulation strategies, and customizable tape storage options.

CoDiPack, the Code Differentiation Package, is an open-source (GPL3) C++ library for algorithmic differentiation based on tape-based operator overloading and expression templates. It was introduced as a high-performance framework for reverse-mode differentiation in large-scale software, with design goals of minimal memory consumption, optimal runtime, extensibility, and usability in industrial and HPC codes. Its core implementation emphasizes Jacobi taping, modular tape interfaces, and recursive storage structures, and later work extended the framework to external derivative kernels generated by Enzyme, local adjoints for simultaneous preaccumulations with shared inputs in shared-memory parallel reverse mode, and native complex-number support through aggregated active types (Sagebaum et al., 2017, Sagebaum et al., 2023, Blühdorn et al., 2024, Sagebaum et al., 7 Aug 2025).

1. Origin, purpose, and scope

CoDiPack was presented as a response to a gap among reverse-mode AD tools: primal value taping was available in systems such as ADOL-C, and Jacobi taping with expression templates was available in systems such as adept and dco/c++, but existing implementations were either closed source or lacked essential features and flexibility for large-scale software. CoDiPack therefore focused on a modular, open-source implementation of high-performance reverse-mode AD for C++, explicitly targeting large codes such as SU2 and generic PDE solvers (Sagebaum et al., 2017).

The package is centered on tape-based operator overloading. In the forward execution, active operations are recorded; in reverse interpretation, the tape is traversed backward to propagate adjoints. CoDiPack was initially described as primarily implementing reverse mode via Jacobi taping, while its modular design was intended to support alternative taping schemes, including primal value taping. Later work describes both Jacobian taping and primal value taping within the CoDiPack framework, again with linear and reuse index managers, and frames the library as a platform for mixing operator-overloading AD with external derivative kernels and specialized aggregated types (Sagebaum et al., 2017, Sagebaum et al., 2023).

Its stated application domain is large-scale scientific software, especially CFD adjoints, PDE-constrained optimization, and related HPC workloads. The package has been evaluated on a coupled Burgers PDE example, large SU2 CFD configurations, hybrid MPI+OpenMP discrete adjoints, and synthetic complex-valued Burgers variants, which together place CoDiPack in the context of production-scale reverse accumulation rather than only small benchmark codes (Sagebaum et al., 2017, Blühdorn et al., 2024, Sagebaum et al., 7 Aug 2025).

2. Reverse-mode formulation and architectural core

CoDiPack’s mathematical basis is standard reverse-mode AD. For a function y=f(x)y=f(x), f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m, the adjoint relation is

xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.

For an elemental statement vi+n=φi(ui)v_{i+n}=\varphi_i(u_i), reverse interpretation performs local vector-Jacobian products of the form

vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.

This local update rule is the operational kernel of CoDiPack’s reverse sweep (Sagebaum et al., 2017).

The principal implementation strategy is Jacobi taping with expression templates. Rather than decomposing expressions into many unary and binary temporaries, CoDiPack uses expression templates to represent whole expressions at compile time and evaluates derivatives via a recursive calcGradient(Data, multiplier) interface. This permits compiler inlining, avoids virtual dispatch, and suppresses intermediate temporaries that would otherwise inflate tape size and runtime. The library also provides forward mode, with the forward tape storing tangents and using the same expression-template machinery for local derivative evaluation (Sagebaum et al., 2017).

A central design principle is the separation of concerns among active values, expressions, and tapes. ActiveReal is the active calculation type; it contains primal data and tape-specific metadata, while differentiation logic is delegated to the tape interface. The expression interface provides getValue() and calcGradient(Data, multiplier). Tape interfaces are split into an interaction interface used by ActiveReal and a user-facing control interface exposing operations such as registerInput, registerOutput, setActive, setPassive, and evaluate. This modularity is intended to make additional taping strategies and data streams implementable without redesigning the active value type (Sagebaum et al., 2017).

The tape storage itself is organized as recursive chunk vectors. CoDiPack separates streams with different logical strides: per statement, the number of arguments; per argument, Jacobians and indices; and, where needed, user-defined function descriptors and their positions. These streams are combined into a recursive structure in which operations are applied at the root and propagated through the hierarchy so that all streams advance consistently. The reported chain is User Function Vector → Statement Vector → Argument Vector → linear index. The design goal is cache-friendly contiguous storage with low padding overhead (Sagebaum et al., 2017).

For Jacobi taping, the per-statement storage model is explicit. For an elemental statement with kk active inputs, the tape stores kk doubles for local Jacobians, kk indices for arguments, and $1$ byte for the number of arguments, yielding approximately

mentry12k+1 bytes,m_{\text{entry}} \approx 12k + 1 \text{ bytes},

and hence total memory approximately

f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m0

This storage model is one of the reasons expression-template collapse of whole expressions matters: reducing the number of tape entries and the effective arity directly reduces tape volume (Sagebaum et al., 2017).

3. Tape variants, indexing strategies, and baseline performance

CoDiPack exposes multiple reverse-mode tape types that combine allocation policy, bounds checking, and index management. Linear indexing reconstructs left-hand-side indices during reverse evaluation and supports c-like memory operations such as memcpy, but it requires adjoint vectors whose size is approximately the number of statements. Index reuse recycles destroyed indices via a stack-based manager, which reduces adjoint-vector size and usually accelerates reverse interpretation through better cache behavior, but it prohibits c-like memory operations and introduces copy/assignment overhead because each ActiveReal must have a unique index (Sagebaum et al., 2017).

Tape type Allocation and checks Indexing
RealReverseUnchecked No bounds checking; user must preallocate tape memory; fastest recording Linear indexing
RealReverse Chunk-based allocation on the fly with bounds checking Linear indexing
RealReverseIndexUnchecked No bounds checking; user-managed allocation Index reuse
RealReverseIndex Chunk-based allocation; reduced memory footprint; safer Index reuse

A typical reverse-mode workflow is explicit. One replaces double with ActiveReal in the target region, obtains a tape handle, brackets the taped region with setActive() and setPassive(), registers inputs and outputs with registerInput and registerOutput, records the primal execution once, seeds output adjoints, and then calls evaluate() to propagate adjoints in reverse. CoDiPack thereby exposes an explicit user interface for delimiting active regions and controlling tape evaluation rather than relying on implicit global tracing (Sagebaum et al., 2017).

The 2017 performance study used a coupled Burgers PDE on a f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m1 grid with 32 iterations and an SU2 discrete-adjoint application for Reynolds-averaged Navier–Stokes equations. On the Burgers problem, total derivative runtime relative to the primal was reported as factors of approximately f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m2 in the single-process case and approximately f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m3 in the multi-process case. Chunk-size sensitivity was also examined; interpretation time improved as chunk size increased up to about f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m4 million entries, and the default was set to f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m5 entries to balance cases. Configuration switches such as expression argument-domain checks, invalid/zero Jacobian handling, tape activity checks, and “skip zero adjoints” produced little overhead in bandwidth-limited runs and about f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m6 overhead when all checks were enabled in compute-limited single runs (Sagebaum et al., 2017).

In SU2, CoDiPack was evaluated on an inviscid supersonic LM1021 aircraft configuration with f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m7 interior elements and f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m8 boundary elements. Without preaccumulation, recording cost per flow iteration was reported as approximately f:RnRmf:\mathbb{R}^n\to\mathbb{R}^m9 to xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.0 primal, and interpretation as approximately xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.1 to xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.2 primal. Preaccumulation increased recording time by about xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.3 but halved interpretation time, reducing overall adjoint runtime by nearly xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.4 and significantly reducing memory usage. No degradation was observed in parallel runs on 16 cores; the reported behavior was slightly better than serial, which was interpreted as consistent with bandwidth-limited execution (Sagebaum et al., 2017).

4. Shared-memory parallelism, preaccumulation, and local adjoints

Preaccumulation is a central optimization in CoDiPack. Instead of keeping a large recorded subgraph on the tape, a subgraph xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.5 with inputs xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.6 and outputs xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.7 is recorded, its Jacobian xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.8 is computed using forward or reverse AD with unit seeds, and the subgraph tape is then discarded and replaced by xˉ=(dfdx)Tyˉ,xˉi=j=1mfjxiyˉj.\bar{x}=\left(\frac{\mathrm{d}f}{\mathrm{d}x}\right)^T \bar{y}, \qquad \bar{x}_i=\sum_{j=1}^m \frac{\partial f_j}{\partial x_i}\,\bar{y}_j.9 together with input/output binding information. This shortens the tape and accelerates reverse evaluation, especially in workflows where the tape is recorded once but evaluated many times (Blühdorn et al., 2024).

In hybrid MPI+OpenMP reverse mode, however, simultaneous thread-local preaccumulations become problematic when the corresponding subgraphs share inputs. CoDiPack and OpDiLib originally supported thread-local tape recording and synchronized parallel reverse evaluation with a single shared adjoint vector. When multiple threads preaccumulate code sections that share inputs, both threads attempt to read, zero, seed, and update the same adjoint memory location in the shared vector during the multiple internal passes used to assemble the Jacobian. The analysis explicitly states that atomic updates do not solve this problem, because preaccumulation is a multi-pass procedure with repeated resets and updates, and interleaving independent subgraphs produces a nonsensical mixture of Jacobian entries (Blühdorn et al., 2024).

The local-adjoint formulation introduces thread-private adjoint containers. For vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)0 with Jacobian vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)1, reverse accumulation uses

vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)2

If thread vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)3 computes local input-adjoint contributions vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)4 over its own subset of outputs, the correct global adjoint is recovered by the reduction

vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)5

CoDiPack’s implementation does not reduce these thread-local quantities into the shared adjoint vector during preaccumulation. Instead, each thread computes its preaccumulated Jacobian using a local container, embeds the resulting Jacobian into the global tape, and then relies on the subsequent global reverse pass to apply all Jacobians correctly (Blühdorn et al., 2024).

Three storage strategies were implemented. The vector-based approach uses dense thread-local vectors indexed by variable IDs, either as a full copy of the shared adjoint vector with memory vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)6 or as a dense vector restricted to the subgraph ID range with memory vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)7. The map-based approach uses std::map<int,double> or std::unordered_map<int,double>, so that only touched IDs allocate entries and memory becomes vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)8 per thread. The hybrid “tape editing” approach first remaps subgraph IDs to a dense range vi+n=φi(ui)v_{i+n}=\varphi_i(u_i)9 using a map, edits the subgraph tape to use the remapped IDs, and then evaluates with a small dense vector, thereby recovering vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.0 access and memory vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.1 (Blühdorn et al., 2024).

Integration required generalizing tape evaluation from

kk7

to

kk8

where the supplied container must provide operator[](id) -> reference to double. PreaccumulationHelper correspondingly acquired finishVectorLocal(), finishMapLocal(), and finishTapeEditedLocal() methods (Blühdorn et al., 2024).

The benchmark campaign used CoDiPack in SU2 on a dual-socket Intel Xeon Gold 6126 system and considered NACA 0012, Onera M6, and HL-CRM. All local-adjoint strategies increased recording time relative to the baseline that disabled preaccumulations with shared inputs; map-based approaches were slower than vector-based ones during recording because of lookup and insertion overhead, while tape editing reduced this overhead by paying the mapping cost once and then reusing dense local vectors. Evaluation time, however, decreased for all local-adjoint strategies because preaccumulated tape sections eliminate atomic updates in reverse passes and shorten the tape. Net runtime improved in all serial and most parallel cases. The strongest memory behavior came from map-local adjoints and tape editing, whereas vector-local adjoints did not improve memory because duplicating large dense vectors per thread counteracted hybrid memory scaling. On HL-CRM at 192 cores, map-local adjoints used less memory than MPI-only, and on Onera M6 hybrid parallelism with map-local adjoints reached memory parity with MPI-only at 6 threads, whereas the baseline without shared-input preaccumulation could not reach parity at 12 threads (Blühdorn et al., 2024).

5. External derivative kernels and Enzyme integration

CoDiPack supports “external function” nodes on the tape, allowing a region of primal code to be recorded as a single node instead of as many small operator entries. The 2023 Enzyme integration added EnzymeExternalFunctionHelper, a helper that automatically binds Enzyme-generated derivative callbacks to such nodes during CoDiPack recording. The intended use case is selective replacement of hot taped regions—such as large loop kernels, stencils, or library calls—by compact compiler-generated derivative kernels while retaining CoDiPack’s operator-overloading workflow for the surrounding code (Sagebaum et al., 2023).

The external-function ABI is explicit. The primal function has the signature

kk9

and derivative callbacks implement forward and reverse actions through func_d and func_b, corresponding to vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.2 and vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.3. Enzyme generates these functions at compile time through LLVM IR by means of __enzyme_fwddiff and __enzyme_autodiff. EnzymeExternalFunctionHelper<RealType> provides addToTape<func>(x_ptr, m, y_ptr, n), addInput(ad_val), addOutput(ad_val), and getExternalFunctionUserData(), which exposes a codi::ExternalFunctionUserData object for metadata transport (Sagebaum et al., 2023).

The effect on tape semantics is that, during recording, CoDiPack stores a single external-function node carrying input identifiers, output identifiers, optional primal values, user data payload, and pointers to derivative callbacks. During reverse interpretation, the tape calls func_b once with primal inputs, input adjoints, primal outputs, output adjoint seeds, and user data; the resulting adjoints are accumulated into the tape’s adjoint vector for each input identifier. This preserves the chain rule ordering of the surrounding tape (Sagebaum et al., 2023).

The reported memory model assigns each external-function node an overhead of approximately vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.4 bytes in addition to per-input identifiers and, depending on tape configuration, optional primal values. This makes large kernels attractive and very small functions potentially unattractive. For primal value tapes, an option described as primal recovery (“handling=on”) avoids storing input primals twice by reconstructing them from the tape’s primal-value vector during reverse evaluation, although the paper notes that this can introduce random-access penalties (Sagebaum et al., 2023).

The synthetic benchmark again used coupled Burgers’ equations on a vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.5 grid, here with 16 time steps and Clang 14, averaged over 20 runs. Replacing the 2D update loop with Enzyme- or Tapenade-generated kernels reduced overall memory from vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.6 GB to about vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.7 GB. Recording time decreased by approximately vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.8 in the single-process configuration and approximately vˉj+=vˉi+nφivj(ui),ji.\bar{v}_j \mathrel{+}= \bar{v}_{i+n}\,\frac{\partial \varphi_i}{\partial v_j}(u_i), \qquad j \prec i.9 in the 24-core multi-process configuration. Reverse time improved by approximately kk0 in the single-process case and approximately kk1 in the multi-process case. The stated explanation is reduced tape traffic, improved data locality, and lower memory-bandwidth pressure (Sagebaum et al., 2023).

The paper also emphasizes correctness conditions. The ABI must be matched exactly; Enzyme’s enzyme_dup and enzyme_const annotations must reflect activity correctly; and side effects outside the designated input/output buffers and user data can compromise derivative correctness. Pointer aliasing must also be handled consistently. These constraints place the mechanism in the category of controlled tape compression rather than transparent arbitrary source transformation (Sagebaum et al., 2023).

6. Complex numbers and aggregated active types

CoDiPack was later extended to support complex numbers natively within its expression-template framework. The implementation generalizes beyond std::complex by introducing AggregatedActiveType and AggregatedTypeTraits: an aggregated type kk2 is projected to kk3 by a projection operator kk4. For complex numbers, kk5 and kk6 is represented by two real components. The purpose is to record complex operations as single expression nodes rather than as many scalar operations performed internally by the standard library implementation (Sagebaum et al., 7 Aug 2025).

This change is significant because untreated complex arithmetic decomposes into many scalar tape operations. The paper reports that std::complex tanh requires 138 bytes on a Jacobian-taping tool when decomposed, whereas a specialized complex tanh requires 58 bytes, a 55% reduction. Likewise, the expression kk7 records 232 bytes when decomposed and 106 bytes as a single expression, approximately a 50% reduction. The stated rule of thumb is about 40% tape-memory reduction for complex-heavy code when expression templates capture complex operations as single nodes (Sagebaum et al., 7 Aug 2025).

The AD rules are formulated by viewing complex maps as real maps on kk8. For multiplication kk9, with kk0 and kk1, the real Jacobians are

kk2

The paper similarly provides explicit Jacobians for division, conjugation, real, imag, magnitude, squared magnitude, argument, normalization, exp, log, sin, and cos. For mixed real/complex binary operations, reverse accumulation into a real scalar argument uses only the real part of the complex reverse update. This point is presented as a correction to a naive reverse rule (Sagebaum et al., 7 Aug 2025).

The integration required several architectural changes. std::complex<ActiveType> is specialized so that complex expressions participate directly in CoDiPack’s expression-template system; on compilers that struggle with this specialization, the documented workaround is -DCODI_SpecializeStdComplex=0 together with codi::ActiveComplex<Real>. real() and imag() are injected as expression members through ExpressionMemberOperations. On the Jacobian tape, complex assignments are stored as separate statements per output component in order to preserve the existing tape layout and zero-Jacobian compression. On the primal value tape, the implementation was refactored to use two stacks—a header stack and a byte-stream stack—because larger function-pointer signatures had caused register spills and an approximately 10% slowdown under the x86-64 ABI; the refactor improved recording by about 15% while increasing overall tape size by about 5% (Sagebaum et al., 7 Aug 2025).

The benchmark problem was a complex-valued coupled Burgers system on a kk3 grid with 16 iterations, run on dual AMD EPYC 7262 hardware with gcc 15.1 and averaged over 20 runs. For Jacobian taping, complex handling reduced tape memory from 10,188.80 MB to 6,082.56 MB in the linear case, a reduction of about 40.3%, and from 8,990.72 MB to 5,765.12 MB in the reuse case, a reduction of about 35.9%. For primal value taping, the reductions were from 12,185.60 MB to 4,075.52 MB, about 66.5%, and from 10,997.76 MB to 3,758.08 MB, about 65.8%. The paper lists coverage for +, -, *, /, pow, polar, and unary operations including real, imag, abs, arg, norm, conj, proj, exp, log, log10, sqrt, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, and atanh (Sagebaum et al., 7 Aug 2025).

The implementation explicitly treats real(), imag(), abs(), norm(), and arg() as kk4 maps rather than as holomorphic complex derivatives. It also notes branch cuts for log and arg on the negative real axis and undefined derivatives at kk5 for arg, kk6, and normalization. These are not CoDiPack-specific pathologies; they are the analytic edge cases that must be respected by any real-valued AD treatment of complex arithmetic (Sagebaum et al., 7 Aug 2025).

7. Limitations, misconceptions, and position in the AD ecosystem

A recurring misconception is that CoDiPack is simply a generic operator-overloading tool without distinctive tape engineering. The published work instead emphasizes specific implementation choices: recursive chunk vectors, explicit index strategies, modular separation between active values and tapes, preaccumulation support, external-function integration, and aggregated active types. These are presented as mechanisms for controlling tape size, memory traffic, and reverse-interpretation cost in large software systems rather than only as API features (Sagebaum et al., 2017, Sagebaum et al., 2023, Sagebaum et al., 7 Aug 2025).

Another misconception is that atomic updates suffice for all shared-memory reverse-mode races. The local-adjoint analysis explicitly rejects this for simultaneous preaccumulations with shared inputs. Atomics can protect individual additions, but they do not prevent interference among the repeated resets, seeds, and updates that occur inside multi-pass preaccumulation. Local adjoints remove that internal race, but the same work also states that global reverse evaluation may still require race mitigation depending on the broader parallel differentiation strategy, which in the SU2 setting is coordinated by OpDiLib (Blühdorn et al., 2024).

The library’s limitations are correspondingly concrete. Performance is sensitive to compiler inlining in expression-template-heavy code. Index reuse reduces adjoint-vector size and often accelerates interpretation, but it prohibits c-like memory operations and complicates copy semantics. External-function nodes incur an approximately 256-byte overhead and can therefore underperform baseline taping on very small functions; derivative correctness also depends on purity, activity annotations, and aliasing discipline. Complex support introduces additional caveats associated with branch cuts, self-references, mixed real/complex adjoints, and undefined derivatives at singular points (Sagebaum et al., 2017, Sagebaum et al., 2023, Sagebaum et al., 7 Aug 2025).

Within the AD ecosystem, CoDiPack is positioned against ADOL-C, adept, and dco/c++ in the 2017 comparison, and later work also relates its specialized-operation strategy to dco/c++, Adept, Stan Math, and Eigen-AD. ADOL-C is described as primal value taping, mature, and open source, but with high runtime and memory overhead on large-scale problems. adept is described as open-source Jacobi taping with expression templates and high performance, but without higher-order differentiation and vector/tapeless forward modes. dco/c++ is described as feature-rich but proprietary. CoDiPack’s distinguishing claim is that it combines open-source availability with Jacobi taping, modular extensibility, tape-engineering choices tailored to HPC, and a framework into which additional strategies—primal taping, local-adjoint preaccumulation, compiler-generated external kernels, and aggregated types such as complex numbers—can be integrated without abandoning the core operator-overloading workflow (Sagebaum et al., 2017, Blühdorn et al., 2024, Sagebaum et al., 2023, Sagebaum et al., 7 Aug 2025).

Topic to Video (Beta)

No one has generated a video about this topic yet.

Whiteboard

No one has generated a whiteboard explanation for this topic yet.

Follow Topic

Get notified by email when new papers are published related to CoDiPack.