Files
Jungfraujoch/image_analysis/CMakeLists.txt
T
leonarski_fandClaude Opus 5 86b7e07131 Drop Ceres' LAPACK, METIS and SuiteSparse back-ends: nothing calls them
Every Ceres solve in the project selects a dense linear solver explicitly --
DENSE_QR in PostRefine, RingOptimizer and StillsPartialityRefine,
DENSE_NORMAL_CHOLESKY in XtalOptimizer, DENSE_SCHUR in GeometryRefiner -- and
none of the seven Solver::Options sites sets dense_linear_algebra_library_type,
which defaults to EIGEN. ceres::Covariance, the other SuiteSparse consumer, is
never instantiated. So no sparse factorization and no LAPACK call is reachable.

Measured rather than argued: an LD_PRELOAD shim exporting dgeqrf_, dpotrf_,
dtrtrs_, spotrf_ and METIS_NodeND, each aborting on entry, let a full mx run
(1800 frames, index -> refine -> integrate -> scale -> merge) finish with every
counter at zero.

The change is a no-op on results. Baseline and modified binaries produce
byte-identical run.mtz, run.hkl and run.cif on a rotation dataset at -N 1, and
two baseline runs give that same signature, so the comparison is meaningful.
Only the DATE= stamp in the report differs.

What it buys is six fewer shared libraries: libopenblas and libmetis, plus
libgfortran, libquadmath, libgomp and libz behind them. rugnux links its own
zlib statically -- zero undefined zlib symbols before and after -- so the
dynamic libz only ever arrived through OpenBLAS.

It also makes the build reproducible across hosts. SUITESPARSE defaults ON and
self-disables only when its probe fails, so a machine where SuiteSparse_DIR is
not found already compiled CERES_NO_SUITESPARSE while one carrying the
SuiteSparse CMake config would not have. Pinning it removes that divergence,
and shortens what a cross-compiled aarch64 build has to supply.

EIGENSPARSE stays ON, so a sparse solver remains available if one is ever
selected, without an external library.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQjneRUssfhi1k9rq8Ts3h
2026-08-25 15:58:34 +02:00

92 lines
4.9 KiB
CMake

# Eigen has to be resolved BEFORE Ceres is added below. Whichever find_package(Eigen3) runs first
# creates the Eigen3::Eigen imported target, and every later one leaves an existing target alone --
# so the first one wins for the whole build, Ceres included. See the guard after Ceres for why that
# matters here.
FIND_PACKAGE(Eigen3 3.4 REQUIRED NO_MODULE) # provides Eigen3::Eigen
SET(PROVIDE_UNINSTALL_TARGET OFF)
# Force Ceres to build WITHOUT its own CUDA support: Jungfraujoch never uses Ceres' GPU solvers, so
# this just drops unused code and build time (Ceres would otherwise enable CUDA whenever a toolkit is
# found). See the Eigen note in the top-level CMakeLists for the related Windows/cmake history.
SET(USE_CUDA OFF CACHE BOOL "Force Ceres to build without CUDA (Jungfraujoch does not use Ceres' GPU solvers)" FORCE)
# Ceres' sparse and LAPACK back-ends are dead weight here: every Ceres solve in this project picks a
# DENSE solver explicitly (DENSE_QR / DENSE_NORMAL_CHOLESKY / DENSE_SCHUR) and none sets
# dense_linear_algebra_library_type, which defaults to EIGEN -- so no sparse factorization and no
# LAPACK call is ever reachable. Left at their defaults they cost rugnux six shared libraries
# (libopenblas, libmetis, and libgfortran/libquadmath/libgomp/libz behind them), and SuiteSparse
# turns itself on or off depending on what happens to sit on the build host, so two machines build
# different Ceres from the same source. Pinning all three off leaves the results bit-identical.
# EIGENSPARSE stays ON: it keeps a sparse implementation available without an external library.
SET(SUITESPARSE OFF CACHE BOOL "Ceres: no sparse solver is ever selected" FORCE)
SET(LAPACK OFF CACHE BOOL "Ceres: dense solves go through Eigen, not LAPACK" FORCE)
SET(EIGENMETIS OFF CACHE BOOL "Ceres: METIS ordering only applies to sparse factorization" FORCE)
# Prevent MKL from being found (guarantees no MKL)
SET(CMAKE_DISABLE_FIND_PACKAGE_MKL TRUE)
SET(CERES_THREADING_MODEL "CXX_THREADS")
FetchContent_Declare(
ceres
GIT_REPOSITORY https://github.com/ceres-solver/ceres-solver
GIT_TAG 0c70ed3
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(ceres)
# Ceres asks for Eigen with a version RANGE -- find_package(Eigen3 3.3.4...5 NO_MODULE) -- and
# Eigen's own Eigen3ConfigVersion.cmake rejects any range whose two endpoints differ in major
# version. A 3.4 Eigen therefore reports itself INCOMPATIBLE with Ceres' query and the search falls
# through to whatever older Eigen comes next on the prefix path (a distro eigen3-devel, say). Having
# resolved Eigen above, Ceres inherits our Eigen3::Eigen instead, because a generated targets file
# returns early when its targets already exist. If that ever stops holding, Ceres compiles against
# one Eigen and the rest of the project against another; the two versions' identically mangled
# template instantiations are then merged at link time -- an ODR violation whose symptom is a crash
# inside Eigen. Refuse to build it.
GET_PROPERTY(CERES_IMPORTED_TARGETS DIRECTORY ${ceres_SOURCE_DIR} PROPERTY IMPORTED_TARGETS)
IF ("Eigen3::Eigen" IN_LIST CERES_IMPORTED_TARGETS)
MESSAGE(FATAL_ERROR "Ceres created an Eigen3::Eigen of its own instead of using the Eigen this "
"project resolved. Two Eigen versions in one binary is undefined behaviour. Leave a "
"single Eigen on the find_package path, or point CMAKE_PREFIX_PATH / Eigen3_DIR at the "
"one to use.")
ENDIF()
ADD_LIBRARY(JFJochImageAnalysis STATIC
MXAnalysisWithoutFPGA.cpp
MXAnalysisWithoutFPGA.h
MXAnalysisAfterFPGA.h
MXAnalysisAfterFPGA.cpp
IndexAndRefine.cpp
IndexAndRefine.h
dark_mask_analysis/DarkMaskAnalysis.cpp
dark_mask_analysis/DarkMaskAnalysis.h
beam_stop/ShadowFinder.cpp
beam_stop/ShadowFinder.h
$<$<BOOL:${JFJOCH_USE_CUDA}>:beam_stop/ShadowAccumulatorGPU.cu>
beam_stop/ShadowAccumulatorGPU.h
rotation_indexer/RotationIndexer.cpp
rotation_indexer/RotationIndexer.h
WriteReflections.cpp
WriteReflections.h
LoadFCalcFromMtz.cpp
LoadFCalcFromMtz.h
UpdateReflectionResolution.cpp
UpdateReflectionResolution.h
IntegrationOutcome.h
rotation_indexer/RotationIndexerCounter.cpp
rotation_indexer/RotationIndexerCounter.h)
ADD_SUBDIRECTORY(spot_finding)
ADD_SUBDIRECTORY(bragg_integration)
ADD_SUBDIRECTORY(bragg_prediction)
ADD_SUBDIRECTORY(indexing)
ADD_SUBDIRECTORY(geom_refinement)
ADD_SUBDIRECTORY(lattice_search)
ADD_SUBDIRECTORY(scale_merge)
ADD_SUBDIRECTORY(image_preprocessing)
ADD_SUBDIRECTORY(azint)
ADD_SUBDIRECTORY(roi)
TARGET_LINK_LIBRARIES(JFJochImageAnalysis JFJochAzIntEngine JFJochROIIntegration JFJochImagePreprocessing JFJochBraggPrediction JFJochBraggIntegration JFJochLatticeSearch JFJochIndexing JFJochSpotFinding JFJochCommon JFJochGeomRefinement JFJochScaleMerge gemmi)