Commit 9cb6e47e authored by Michael Tesch's avatar Michael Tesch
Browse files

Fix std::complex<dual<T>> with libc++ 20 (Xcode 17)

parent 7ab7ceb4
Loading
Loading
Loading
Loading

.appveyor.yml

deleted100644 → 0
+0 −38
Original line number Diff line number Diff line
version: 0.2.{build}
clone_folder: c:\projects\cppduals
clone_depth: 3

image:
- Visual Studio 2017
#- Visual Studio 2022

configuration:
- Release
#- Debug # someone with a debugger please investigate this :)

# Do not build feature branch with open Pull Requests
skip_branch_with_pr: true

init:
- echo %APPVEYOR_BUILD_WORKER_IMAGE%
- if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set generator="Visual Studio 15 2017" )
- if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( set generator="Visual Studio 17 2022" )
- echo %generator%

before_build:
- cmd: |-
    mkdir build
    cd build
    cmake --version
    cmake .. -G %generator% -DCPPDUALS_TESTING=ON
    pwd
    ls

build:
  project: c:\projects\cppduals\build\cppduals.sln
  verbosity: minimal
#  parallel: true

test_script:
- pwd
- ctest -C Debug -VV
+0 −7
Original line number Diff line number Diff line
@@ -119,10 +119,3 @@ release:
        --tag-name $CI_COMMIT_TAG \
        --assets-link "{\"name\":\"${HEADER_ONLY_PACKAGE}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${HEADER_ONLY_PACKAGE}\"}"
sast:
  variables:
    SAST_DEFAULT_ANALYZERS: flawfinder
  stage: test

include:
- template: Security/SAST.gitlab-ci.yml

CLAUDE.md

0 → 100644
+83 −0
Original line number Diff line number Diff line
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

cppduals is a header-only C++17 template library for dual number arithmetic and forward-mode automatic differentiation. Dual numbers have the form `a + b·ε` where `ε² = 0`, enabling exact derivative computation. Supports recursive nesting (`dual<dual<T>>`) for higher-order derivatives and composition with `std::complex`.

## Build Commands

The project uses CMake (3.14+) with a Makefile wrapper for convenience.

```bash
# Debug build with Clang (includes coverage support)
make clang

# Release build with Clang
make clangr

# Run tests (builds clangr first)
make test

# Coverage report
make cov          # Clang — report at Build-clang/coverage/html/index.html
make cov-gcc      # GCC — report at Build-cc/coverage/html/index.html

# Manual CMake build
cmake -Bbuild -DCPPDUALS_TESTING=ON -DCPPDUALS_BENCHMARK=ON
cmake --build build
CTEST_OUTPUT_ON_FAILURE=y cmake --build build --target test

# Run a single test (after building)
./Build-clangr/tests/test_dual        # or any test binary in tests/
ctest --test-dir Build-clangr -R test_dual   # by name pattern

# Clean everything
make distclean
```

Build output goes to `Build-clang/`, `Build-clangr/`, `Build-cc/`, `Build-ccr/`, `Build-osx/` depending on target.

## Architecture

**Core library** (`duals/`): Header-only, no compilation needed for library users.

- `duals/dual` — Main `duals::dual<T>` class template with all math functions (sin, cos, exp, log, pow, etc.) and operator overloads. This is the primary public API (~1900 lines).
- `duals/dual_eigen` — Eigen matrix library integration: NumTraits, type promotion, and vectorized packet operations for dual types.
- `duals/dual_fmt.h` — Optional fmt library formatting support.
- `duals/arch/{SSE,AVX,NEON}/` — Hand-coded SIMD vectorization for `dual<float>`, `dual<double>`, and `complex<dual<>>`.

**Tests** (`tests/`): Google Test-based. Key test files: `test_dual.cpp` (core), `test_eigen.cpp` (Eigen integration), `test_packets.cpp` (SIMD), `test_cdual.cpp` (complex-dual), `test_funcs.cpp` (math functions), `test_solve.cpp` (linear solvers), `test_expm.cpp` (matrix exponential).

**Dependencies** are fetched via CMake FetchContent in `thirdparty/CMakeLists.txt`: Google Test, Eigen, fmt, Google Benchmark.

## Versioning and Releases

The version is defined in the top-level `CMakeLists.txt` (`project(cppduals VERSION X.Y.Z ...)`). GitLab CI (`.gitlab-ci.yml`) handles packaging and release creation automatically when a tag is pushed:

- **upload** stage: creates a header-only tarball and publishes it to the GitLab package registry
- **release** stage: creates a GitLab release linked to the package

To cut a new release:

```bash
# 1. Update the VERSION in CMakeLists.txt
#    project(cppduals VERSION 0.6.4 ...)

# 2. Commit the version bump
git commit -am "Bump version to 0.6.4"

# 3. Tag and push — CI does the rest
git tag v0.6.4
git push origin master --tags
```

The tag name must match the format `vX.Y.Z` (e.g. `v0.6.4`). CI uses `$CI_COMMIT_TAG` to derive the package version.

## Code Conventions

- Comment WHY, not WHAT.
- License: MPL 2.0.
- Must compile on GCC, Clang, and MSVC.
- Performance goal: match or exceed `std::complex<>` speed for equivalent operations.
+201 −50
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ _LIBCPP_END_NAMESPACE_STD

#include <limits>
#include <type_traits>
#include <cmath>

#endif // PARSED_BY_DOXYGEN

@@ -1010,57 +1011,210 @@ template <class DT> _LIBCPP_CONSTEXPR inline _LIBCPP_HIDE_FROM_ABI duals::dual<D
template <class DT> _LIBCPP_CONSTEXPR inline _LIBCPP_HIDE_FROM_ABI duals::dual<DT> __constexpr_logb(duals::dual<DT> a);
#endif

// export functions to the std namespace
using duals::exp;
using duals::log;
using duals::log10;
using duals::log2;
using duals::logb;
using duals::pow;
using duals::sqrt;
using duals::cbrt;
using duals::sin;
using duals::cos;
using duals::tan;
using duals::asin;
using duals::acos;
using duals::atan;
// Export functions to the std namespace as SFINAE-guarded templates.
// Unlike using-declarations, actual template definitions in namespace std
// are found at instantiation time regardless of when <complex> was parsed.
// This fixes std::complex<dual<T>> with libc++ 20+ where <complex> calls
// std::hypot, std::exp, etc. directly with qualified lookup.

#define CPPDUALS_STD_UNARY(func) \
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0> \
inline T func(const T& x) { return duals::func(x); }

#define CPPDUALS_STD_UNARY_BOOL(func) \
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0> \
inline bool func(const T& x) { return duals::func(x); }

#define CPPDUALS_STD_BINARY(func) \
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0> \
inline T func(const T& x, const T& y) { return duals::func(x, y); }

// single-arg functions returning dual<T>
CPPDUALS_STD_UNARY(exp)
CPPDUALS_STD_UNARY(log)
CPPDUALS_STD_UNARY(log10)
CPPDUALS_STD_UNARY(log2)
CPPDUALS_STD_UNARY(logb)
CPPDUALS_STD_UNARY(sqrt)
CPPDUALS_STD_UNARY(cbrt)
CPPDUALS_STD_UNARY(sin)
CPPDUALS_STD_UNARY(cos)
CPPDUALS_STD_UNARY(tan)
CPPDUALS_STD_UNARY(asin)
CPPDUALS_STD_UNARY(acos)
CPPDUALS_STD_UNARY(atan)
CPPDUALS_STD_UNARY(sinh)
CPPDUALS_STD_UNARY(cosh)
CPPDUALS_STD_UNARY(tanh)
CPPDUALS_STD_UNARY(asinh)
CPPDUALS_STD_UNARY(acosh)
CPPDUALS_STD_UNARY(atanh)
CPPDUALS_STD_UNARY(erf)
CPPDUALS_STD_UNARY(erfc)
CPPDUALS_STD_UNARY(tgamma)
CPPDUALS_STD_UNARY(lgamma)
CPPDUALS_STD_UNARY(abs)
CPPDUALS_STD_UNARY(fabs)
CPPDUALS_STD_UNARY(trunc)
CPPDUALS_STD_UNARY(floor)
CPPDUALS_STD_UNARY(ceil)
CPPDUALS_STD_UNARY(round)

// single-arg functions returning bool
CPPDUALS_STD_UNARY_BOOL(isfinite)
CPPDUALS_STD_UNARY_BOOL(isnormal)
CPPDUALS_STD_UNARY_BOOL(isinf)
CPPDUALS_STD_UNARY_BOOL(isnan)
CPPDUALS_STD_UNARY_BOOL(signbit)

// single-arg function returning int
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0>
inline int fpclassify(const T& x) { return duals::fpclassify(x); }

// two-arg same-type functions returning dual<T>
CPPDUALS_STD_BINARY(hypot)
CPPDUALS_STD_BINARY(copysign)
CPPDUALS_STD_BINARY(fmax)
CPPDUALS_STD_BINARY(fmin)

// two-arg functions with int parameter
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0>
inline T scalbn(const T& x, int ex) { return duals::scalbn(x, ex); }
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0>
inline T ldexp(const T& x, int ex) { return duals::ldexp(x, ex); }
template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0>
inline T frexp(const T& x, int* ex) { return duals::frexp(x, ex); }

// atan2 and pow have complex multi-type overloads with SFINAE constraints
// that interfere with partial ordering when combined with `using std::pow;`
// in the duals::detail implementations. Keep as using-declarations since
// <complex> does not call these with scalar _Tp args.
using duals::atan2;
using duals::hypot;
using duals::scalbn;
using duals::sinh;
using duals::cosh;
using duals::tanh;
using duals::asinh;
using duals::acosh;
using duals::atanh;
using duals::erf;
using duals::erfc;
using duals::tgamma;
using duals::lgamma;
using duals::abs;
using duals::fabs;
using duals::fmax;
using duals::fmin;
using duals::frexp;
using duals::ldexp;
using duals::trunc;
using duals::floor;
using duals::ceil;
using duals::round;
using duals::fpclassify;
using duals::isfinite;
using duals::isnormal;
using duals::isinf;
using duals::isnan;
using duals::signbit;
using duals::copysign;
//using duals::random;
//using duals::random2;
using duals::pow;

#undef CPPDUALS_STD_UNARY
#undef CPPDUALS_STD_UNARY_BOOL
#undef CPPDUALS_STD_BINARY

// TODO: figure out if still needed
namespace __math {
  using duals::isinf;
  template<class T, typename std::enable_if<duals::is_dual<T>::value, int>::type = 0>
  inline bool isinf(const T& x) { return duals::isinf(x); }
}

#ifdef _LIBCPP_END_NAMESPACE_STD
_LIBCPP_END_NAMESPACE_STD
#else
} // namespace std
#endif

// Include <complex> here so that when <duals/dual> is included first,
// <complex> is parsed with the SFINAE templates above already visible.
#include <complex>

// Overloads for std::complex<dual<T>> that bypass the generic <complex>
// implementations. When <complex> is included BEFORE <duals/dual>,
// qualified calls like std::hypot inside <complex> templates can't find
// the dual overloads (qualified dependent lookup only uses definition-time
// names). These complex-level overloads are found directly by user code
// and Eigen (which are parsed after <duals/dual>), avoiding the generic
// implementations entirely.
#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
_LIBCPP_BEGIN_NAMESPACE_STD
#else
namespace std {
#endif

template<class T>
inline duals::dual<T> abs(const complex<duals::dual<T>>& z) {
  return duals::hypot(z.real(), z.imag());
}

template<class T>
inline duals::dual<T> arg(const complex<duals::dual<T>>& z) {
  return duals::atan2(z.imag(), z.real());
}

template<class T>
inline duals::dual<T> norm(const complex<duals::dual<T>>& z) {
  return z.real() * z.real() + z.imag() * z.imag();
}

template<class T>
inline complex<duals::dual<T>> conj(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(z.real(), -z.imag());
}

template<class T>
inline complex<duals::dual<T>> polar(const duals::dual<T>& rho,
                                     const duals::dual<T>& theta = duals::dual<T>()) {
  return complex<duals::dual<T>>(rho * duals::cos(theta), rho * duals::sin(theta));
}

template<class T>
inline complex<duals::dual<T>> exp(const complex<duals::dual<T>>& z) {
  duals::dual<T> e = duals::exp(z.real());
  return complex<duals::dual<T>>(e * duals::cos(z.imag()), e * duals::sin(z.imag()));
}

template<class T>
inline complex<duals::dual<T>> log(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(duals::log(abs(z)), arg(z));
}

template<class T>
inline complex<duals::dual<T>> log10(const complex<duals::dual<T>>& z) {
  return log(z) / duals::dual<T>(duals::log(duals::dual<T>(10)));
}

template<class T>
inline complex<duals::dual<T>> sqrt(const complex<duals::dual<T>>& z) {
  duals::dual<T> r = abs(z);
  duals::dual<T> nr = duals::sqrt(r);
  if (nr == duals::dual<T>(0))
    return complex<duals::dual<T>>();
  duals::dual<T> t = arg(z) / duals::dual<T>(2);
  return complex<duals::dual<T>>(nr * duals::cos(t), nr * duals::sin(t));
}

template<class T>
inline complex<duals::dual<T>> pow(const complex<duals::dual<T>>& x,
                                   const complex<duals::dual<T>>& y) {
  return exp(y * log(x));
}

template<class T>
inline complex<duals::dual<T>> sin(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(duals::sin(z.real()) * duals::cosh(z.imag()),
                                 duals::cos(z.real()) * duals::sinh(z.imag()));
}

template<class T>
inline complex<duals::dual<T>> cos(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(duals::cos(z.real()) * duals::cosh(z.imag()),
                                 -duals::sin(z.real()) * duals::sinh(z.imag()));
}

template<class T>
inline complex<duals::dual<T>> sinh(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(duals::sinh(z.real()) * duals::cos(z.imag()),
                                 duals::cosh(z.real()) * duals::sin(z.imag()));
}

template<class T>
inline complex<duals::dual<T>> cosh(const complex<duals::dual<T>>& z) {
  return complex<duals::dual<T>>(duals::cosh(z.real()) * duals::cos(z.imag()),
                                 duals::sinh(z.real()) * duals::sin(z.imag()));
}

template<class T>
inline complex<duals::dual<T>> tan(const complex<duals::dual<T>>& z) {
  return sin(z) / cos(z);
}

template<class T>
inline complex<duals::dual<T>> tanh(const complex<duals::dual<T>>& z) {
  return sinh(z) / cosh(z);
}

#ifdef _LIBCPP_END_NAMESPACE_STD
@@ -1073,7 +1227,6 @@ _LIBCPP_END_NAMESPACE_STD
/// Implementations
/// /// /// /// /// /// /// /// /// /// /// /// /// /// ///

#include <cmath>
#include <cerrno>

#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
@@ -1586,8 +1739,6 @@ DT random(DT a = DT(0,0), DT b = DT(1,0)) {

} // namespace duals

#include <complex>

namespace duals {
typedef std::complex<dualf> cdualf;
typedef std::complex<duald> cduald;
+1 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ set (OPT_FLAGS "${OPT_FLAGS};-DCPPDUALS_VECTORIZE_CDUAL")
set (ALL_TESTS
  test_dual test_cdual test_funcs test_eigen test_packets
  test_vectorize test_solve test_expm test_1 test_fmt
  test_include_order
  example
  )
set (ALL_TEST_BINS )
Loading