Files
Jungfraujoch/image_analysis/geom_refinement/Dual.h
T
leonarski_f a395f358ef
Build Packages / Create release (push) Successful in 17s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m22s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m37s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m33s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 10m39s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 11m4s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 13m19s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 17m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 18m49s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 19m10s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m31s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 18m54s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m45s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m20s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m37s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m6s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 19m49s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 20m29s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m2s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m27s
Build Packages / Unit tests (push) Successful in 1h18m12s
1.0.0-rc.174 (#84)
* Rugnux: Performance improvements on GPU and CPU (more of the pre-scan and of scaling on the GPU, faster CPU spot finding and crystal refinement), with unchanged results.
* Rugnux: More robust processing - patches of persistently hot pixels are masked, an inconsistent merge triggers a retry at the measured beam centre, and builds targeting different CPU levels give the same results.
* Rugnux: Improved scaling and merging - reflections with an overloaded pixel are dropped, as in XDS, sparse rotation sweeps are scaled more reliably, and French-Wilson amplitudes use an anisotropic Wilson prior.
* Rugnux: Improved space-group determination - glide planes in groups without a centre of symmetry, screw axes from short or weak axial rows kept when a higher group is adopted, and more reliable decisions on twinned and pseudo-symmetric crystals.
* Rugnux: Improved small-molecule processing - spots that grow wider than the integration disk and split spots are integrated over their measured footprint, sparse lattices are integrated on every frame, and the `.hkl` file holds unmerged scaled reflections (SHELX HKLF 4).
* Rugnux: Reads Rigaku d*TREK SMV images (Saturn CCD), including detector 2theta and encoded pixel overflows; home-source (rotating-anode) datasets were added to the validation battery.
* jfjoch_viewer: Fixed processing failing at the end with "Wrong JPEG library version" on Linux; the merge window shows the space group with proper subscripts and a checklist of crystal pathologies.

Reviewed-on: #84
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-10-06 14:03:18 +02:00

143 lines
5.6 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
// A forward-mode dual number with N derivative lanes: a value and its gradient with respect to N
// parameters. The residuals of the crystal refinement are written as templates over their scalar type,
// so the same code runs on a plain double and on this. The value part of every operation is the plain
// double arithmetic of the same expression - written the way ceres::Jet writes it, division through the
// reciprocal - so a residual evaluated on a Dual has the same value as on a Jet.
#include <cmath>
#include <limits>
#include <Eigen/Core>
template<int N>
struct Dual {
double a = 0.0;
double v[N] = {};
Dual() = default;
Dual(double value) : a(value) {} // NOLINT: implicit, a constant is a dual with zero derivatives
static Dual Variable(double value, int lane) {
Dual d(value);
d.v[lane] = 1.0;
return d;
}
Dual &operator+=(const Dual &o) { a += o.a; for (int i = 0; i < N; i++) v[i] += o.v[i]; return *this; }
Dual &operator-=(const Dual &o) { a -= o.a; for (int i = 0; i < N; i++) v[i] -= o.v[i]; return *this; }
Dual &operator*=(const Dual &o) { *this = *this * o; return *this; }
Dual &operator/=(const Dual &o) { *this = *this / o; return *this; }
friend Dual operator+(const Dual &x) { return x; }
friend Dual operator-(const Dual &x) {
Dual r(-x.a);
for (int i = 0; i < N; i++) r.v[i] = -x.v[i];
return r;
}
friend Dual operator+(const Dual &x, const Dual &y) {
Dual r(x.a + y.a);
for (int i = 0; i < N; i++) r.v[i] = x.v[i] + y.v[i];
return r;
}
friend Dual operator+(const Dual &x, double s) { Dual r = x; r.a += s; return r; }
friend Dual operator+(double s, const Dual &x) { Dual r = x; r.a += s; return r; }
friend Dual operator-(const Dual &x, const Dual &y) {
Dual r(x.a - y.a);
for (int i = 0; i < N; i++) r.v[i] = x.v[i] - y.v[i];
return r;
}
friend Dual operator-(const Dual &x, double s) { Dual r = x; r.a -= s; return r; }
friend Dual operator-(double s, const Dual &x) {
Dual r(s - x.a);
for (int i = 0; i < N; i++) r.v[i] = -x.v[i];
return r;
}
friend Dual operator*(const Dual &x, const Dual &y) {
Dual r(x.a * y.a);
for (int i = 0; i < N; i++) r.v[i] = x.a * y.v[i] + x.v[i] * y.a;
return r;
}
friend Dual operator*(const Dual &x, double s) {
Dual r(x.a * s);
for (int i = 0; i < N; i++) r.v[i] = x.v[i] * s;
return r;
}
friend Dual operator*(double s, const Dual &x) { return x * s; }
friend Dual operator/(const Dual &x, const Dual &y) {
const double y_inv = 1.0 / y.a;
const double q = x.a * y_inv;
Dual r(q);
for (int i = 0; i < N; i++) r.v[i] = (x.v[i] - q * y.v[i]) * y_inv;
return r;
}
friend Dual operator/(const Dual &x, double s) {
const double s_inv = 1.0 / s;
return x * s_inv;
}
friend Dual operator/(double s, const Dual &y) {
const double y_inv = 1.0 / y.a;
const double d = -s * y_inv * y_inv;
Dual r(s * y_inv);
for (int i = 0; i < N; i++) r.v[i] = d * y.v[i];
return r;
}
friend bool operator<(const Dual &x, const Dual &y) { return x.a < y.a; }
friend bool operator>(const Dual &x, const Dual &y) { return x.a > y.a; }
friend bool operator<=(const Dual &x, const Dual &y) { return x.a <= y.a; }
friend bool operator>=(const Dual &x, const Dual &y) { return x.a >= y.a; }
friend bool operator==(const Dual &x, const Dual &y) { return x.a == y.a; }
friend bool operator!=(const Dual &x, const Dual &y) { return x.a != y.a; }
// The chain rule for a function of one argument: value f, derivative df.
Dual Chain(double f, double df) const {
Dual r(f);
for (int i = 0; i < N; i++) r.v[i] = df * v[i];
return r;
}
friend Dual sqrt(const Dual &x) {
const double s = std::sqrt(x.a);
return x.Chain(s, 0.5 / s);
}
friend Dual cos(const Dual &x) { return x.Chain(std::cos(x.a), -std::sin(x.a)); }
friend Dual sin(const Dual &x) { return x.Chain(std::sin(x.a), std::cos(x.a)); }
friend Dual hypot(const Dual &x, const Dual &y, const Dual &z) {
// As ceres::hypot(Jet, Jet, Jet): the value is std::hypot, the derivative x/h dx + y/h dy + z/h dz.
const double h = std::hypot(x.a, y.a, z.a);
Dual r(h);
for (int i = 0; i < N; i++) r.v[i] = x.a / h * x.v[i] + y.a / h * y.v[i] + z.a / h * z.v[i];
return r;
}
friend int fpclassify(const Dual &x) { return std::fpclassify(x.a); }
};
// What Eigen needs to hold a Dual in a fixed-size matrix (the reciprocal basis is built in one).
namespace Eigen {
template<int N>
struct NumTraits<Dual<N>> : GenericNumTraits<double> {
typedef Dual<N> Real;
typedef Dual<N> NonInteger;
typedef Dual<N> Nested;
typedef Dual<N> Literal;
enum {
IsComplex = 0, IsInteger = 0, IsSigned = 1, RequireInitialization = 1,
ReadCost = 1, AddCost = 1, MulCost = 1
};
static inline Real epsilon() { return Real(std::numeric_limits<double>::epsilon()); }
static inline Real dummy_precision() { return Real(1e-12); }
static inline Real highest() { return Real(std::numeric_limits<double>::max()); }
static inline Real lowest() { return Real(-std::numeric_limits<double>::max()); }
static inline int digits10() { return NumTraits<double>::digits10(); }
};
}