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
* 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>
143 lines
5.6 KiB
C++
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(); }
|
|
};
|
|
}
|