Repository navigation
Conversation
Two Fortran INCLUDE files under include/, one per public header: cqr_compact.fi (the portable C API, integer(c_int) functions returning the LAPACK-style info) and cqr_mkl_ext.fi (the MKL-style subroutines, with the MKL_LAYOUT / MKL_UPLO / MKL_SIDE / MKL_TRANSPOSE / MKL_DIAG / MKL_COMPACT_PACK enumerators transcribed from mkl_types.h as enum, bind(c), and integer(c_int) for LP64 MKL_INT). Specific names only, every interface body bind(c) with its own iso_c_binding use statement and implicit none. Each file reads identically as fixed-form and free-form source: statements in columns 7-72, a continued line ends with '&' in column 73 (past fixed form's statement field, a continuation in free form) and its continuation carries '&' in column 6 (a continuation in fixed form, stripped in free form). Fixed-form includers must stay at the standard 72-column line length. The tests call every entry point of both APIs from free-form sources (.f90) and include the .fi files from fixed-form ones (.f) -- compiling both is what enforces the dual-form layout. The compact buffers are built with RESHAPE alone: the first reshape splits the batch index into (lane, group) and PAD fills the partial final group (identity matrices for A, zero columns for B), the second one's ORDER permutes the lane innermost, which is the compact layout; nmat = 3 leaves a padding lane at every width, so the padded-group convention is exercised. The MKL-style tests pack by hand and so link only cqr_mkl_ext, no MKL. New opt-in CMake option CQR_BUILD_FORTRAN_TESTS (needs a Fortran compiler; tests carry the "fortran" CTest label) and a CI job running gfortran against MKL ON and OFF. The session-start hook now also installs gfortran. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
Following MKL's own _lp64/_ilp64 interface-file convention (the
mkl_*_omp_offload_{lp64,ilp64}.f90 pattern of newer oneMKL includes),
the ILP64 selection is the includer's: include exactly one of
cqr_mkl_ext.fi (LP64, the default MKLCompact_INTERFACE) and
cqr_mkl_ext_ilp64.fi (an ilp64 library build). In the ILP64 file the
MKL_INT dummies and info are integer(c_long_long); the enum arguments
stay integer(c_int), since a C enum does not widen under ILP64. The
two files differ only in the header comment and those integer kinds,
and must be edited in step; both file headers say so. The portable
API (cqr_compact.fi) uses plain C int and needs no variant.
The MKL Fortran tests become preprocessed sources (.F90/.F): one
CQR_ILP64 macro, defined by CMake when MKLCompact_INTERFACE=ilp64,
switches the include file and the integer kind of every MKL_INT
actual argument, so the same tests run against either build of the
library. CI's fortran-interface job gains the ilp64 leg.
The full suite (C++ tests included) passes under
-DMKLCompact_INTERFACE=ilp64 with both gcc and clang.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
The free-form test programs no longer carry a test_double/test_single pair. Each declares generic interfaces over the include file's specific names (interface geqrf_compact / procedure sgeqrf_compact, dgeqrf_compact -- the pattern a caller who wants generic entry points would write) and one test body in the work precision wp, which the CQR_SINGLE preprocessor guard sets to c_float (default c_double). CMake compiles each source twice, into _d and _s executables, so both sets of entry points stay covered; the interleave width and tolerance follow wp (storage_size / epsilon). The fixed-form includers keep one precision: their job is only the dual-form layout. Generic resolution requires the actual arguments' rank to match the assumed-size dummies (sequence association does not relax rank there, unlike a call to a specific), so the compact buffers in the tests are rank-1, with the RESHAPE packing flattening its permuted 4-D image. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
The include files now group each d/s pair of interface bodies inside a named generic interface block (interface geqrf_compact ... the two bodies ... end interface), so including a .fi declares both the specific names and a precision-generic one -- geqrf_compact, cqr_mkl_geqrf_compact, ... -- resolved by the kind of the real actual arguments. The bodies are unchanged; a generic interface block that contains them declares the specifics exactly as the plain block did. The dual-form layout is untouched (the new lines need no continuation), and F77 compatibility was never at stake: the names and the interface blocks are already Fortran 90, only the *source form* of the includer is free. One caveat, now documented in the headers: generic resolution matches rank against the assumed-size dummies (sequence association applies only to a call to a specific name), so the compact buffers must be rank-1 when calling through the generics. The free-form tests drop their local generic blocks and call the shipped generics, which puts the generic declarations themselves under test in every precision/interface leg. The ILP64 twin is re-derived and still differs from cqr_mkl_ext.fi only in its header and the MKL_INT integer kinds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
The PAD argument of RESHAPE is placed along the ORDER-permuted
traversal, not in result element order, so the two-stage (and
three-stage) reshape idiom collapses to a single call:
packed = reshape(dense, shape(packed), pad=pad, order=[2,3,1,4])
ORDER interleaves the lanes -- which is the compact layout -- and
PAD's copies fill the padding lanes of the partial final group along
the same walk (identity matrices for A, zero columns for B); verified
against the two-stage result before switching. The unpack helper is
gone too: the padding lanes solve identity systems with zero
right-hand sides, so the computed compact solution compares directly
against pack_c of the exact x, padding included.
The compact buffers get their natural shape back, (V, rows, cols,
ngroups). Rank-changing sequence association does cover such an array
passed to the assumed-size dummies -- but only in a call to a
specific name: generic resolution is TKR-exact, and gfortran rejects
both a whole higher-rank array and a starting-array-element actual
through the generics (sequence association applies only once a
specific has been chosen). The calls therefore go through rank-1
pointer views, p(1:size(a)) => a; the interface-file headers, README
and CLAUDE.md now state the rule precisely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
The portable API carries V, the leading dimensions and the extents as arguments, so its .fi can declare the array dummies in the layout's own shape instead of flat assumed-size: matrices ap(v, ldap, ncols, *) with the group count ceil(nm/v) left assumed (the caller pads a partial final group up to a multiple of v), tau taup(v, min(m, n), *). The named extents document the tuned column-major (and side = 'L') case; other layout or side choices swap in-matrix extents of the caller's array, which is harmless -- an assumed-size dummy's extents never have to agree with the actual's, only the rank (for generic resolution) and storage size matter. Naturally shaped (V, rows, cols, ngroups) arrays now go through the generics as they are, and the portable test drops its rank-1 pointer views; a flat buffer still associates in a call to a specific name. The MKL-style interfaces stay rank-1 assumed-size: there V is a runtime function of the format enumerator, not a dummy, so the compact shape is not expressible -- cqr_mkl_ext.fi's header now says so, and its generics keep taking rank-1 actuals (the ILP64 twin's declarations are untouched, keeping the two in step). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
Follow LAPACK's own convention for flag-dependent extents -- TRSM declares A(LDA,*) precisely because A's column count depends on SIDE -- lifted by the lane dimension: matrices are ap(v, ldap, *), the assumed extent absorbing the columns of every group, and tau is taup(v, min(m, n), *) with the groups assumed. Only the strides are named, and strides do not move with the layout or side flags, so every named extent is exact for every flag choice; the previous declarations named the column-major / side='L' column count, which read as a documented shape but was wrong for layout 'R' or side 'R'. The array dummies are uniformly rank-3 as a result, so the portable generics take rank-3 actuals: a rank-4 (V, ld, cols, groups) batch goes through a rank-3 pointer view folding (columns, groups) into the assumed extent, p(1:v, 1:ld, 1:nc*ng) => a, which is what the portable test now does (tau needs no view -- it is (V, k, groups) already). Specific-name calls still accept any rank by sequence association. The MKL-style interfaces are unchanged, rank-1 assumed-size, V being a runtime function of format there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
The (v, ldap, *) declarations were rank-3 against a conceptually 4-D batch: neither the storage truth (a rank-1 interleaved sequence) nor the conceptual one. A fully honest shaped declaration would need rank 4 with merge()-computed, flag-dependent extents in a public dual-form header -- not worth it. So the compact buffers go back to plain assumed-size ap(*), and the header now records why: their in-memory extents depend on the layout and side flags, so no fixed shape is right for every call (LAPACK's TRSM declares A(LDA,*) for the same reason). Both APIs now share one rule -- generics take rank-1 actuals, a flat buffer or a rank-1 pointer view p(1:size(a)) => a; specifics accept any rank by sequence association -- and the moot lp64/mkl asymmetry note leaves cqr_mkl_ext.fi. The portable test keeps its naturally shaped buffers and single-reshape packing, calling through rank-1 views again; declaration bodies in cqr_compact.fi are back to byte-identical with the pre-shape state, only the header text moved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
Interface files: - The mkl_types.h enumerators are transcribed once, in the new cqr_mkl_enums.fi, and pulled into both MKL-style interface files through a nested INCLUDE -- the one hand-copied, silently-driftable region of the lp64/ilp64 twins is now single-sourced. - tests/check_fi_twins.py (the fortran_fi_twins CTest) enforces the twins' invariant by canonicalizing both files (continuations joined, comments dropped, c_long_long mapped to c_int, use-lists and integer-value declarations compared as name sets) and diffing: "nothing else may differ" is machine-checked instead of prose. - Split declaration lines merged: trsm's five character flags fit one line, geqrf's ap and work share intent(inout), and the ILP64 file groups each body's integer dummies as one c_int (enums) line plus one c_long_long (MKL_INT) line -- the line a reader scans to see what widened. Tests: - The byte-identical scaffolding of the two free-form tests (check, fill, pack_c) moves to tests/test_cqr_fortran_util.inc, INCLUDEd after CONTAINS so wp and the batch parameters host-associate; fill now also hands out the pack paddings. pack_c takes assumed-shape arguments, dropping the ncol dummy whose kind differed between the two tests. - The MKL test now uses the lwork = -1 queries it asserts: each routine's workspace is allocated from its own query into a 1-element probe, one buffer per routine, instead of one shared hand-sized array -- the exact pattern the workspace contract forbids no longer appears in the tree's only Fortran exemplar. The format-error check passes a 1-element placeholder (the buffer is never touched). - The fixed-form tests index their buffers as (lane, row, column) through the specifics' sequence association instead of hand- linearized offsets, use an epsilon-based tolerance, and their comments no longer claim exactness while testing to a tolerance. - CMake pins the fixed-form targets at the 72-column line length the dual-form layout requires (GNU/Intel/LLVMFlang flags) instead of relying on compiler defaults, and registers each test target on the list where it is created. Stale .f90 cross-references fixed. Skipped by choice: sharing between the two fixed-form smoke tests (self-containment is the property they prove), hoisting a reset() helper (costs block-local readability), and width/format constants in the .fi files (new public API surface, a separate decision). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
Merge main (which added ?potrs_compact, ?posv_compact and
?orgqr_compact to both C APIs) and extend the Fortran layer to match,
so the .fi files again cover every public C entry point:
- cqr_compact.fi: orgqr_compact, potrs_compact and posv_compact
generic blocks (potrs/posv mirror sytrsnp/sysvnp; orgqr takes the
geqrf factor and tau, in place).
- cqr_mkl_ext.fi: the cqr_mkl_* counterparts (orgqr with the usual
work/lwork pair); cqr_mkl_ext_ilp64.fi re-derived, and the
fortran_fi_twins check confirms the twins still differ only in
their MKL_INT kinds (193 canonical statements).
- Both free-form tests: potrf + potrs solved against the known x and
the fused posv checked bit-identical to the two calls (the same
pattern as the sysvnp block, per the documented guarantee); orgqr's
explicit Q checked against ormqr('N') applied to a packed identity
batch, with orgqr's own lwork = -1 query sizing its workspace in
the MKL test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds comprehensive Fortran interface support for both the portable C API (
cqr_compact.h) and the MKL-style API (cqr_mkl_ext.h). The interfaces are provided as dual-form include files that read identically as fixed-form and free-form Fortran source, enabling Fortran users to call the library's batched QR, Cholesky, LDL^T, triangular solve, and least-squares solve routines.Key Changes
Fortran Interface Files:
include/cqr_compact.fi: Portable C API interfaces with explicit interleave widthVinclude/cqr_mkl_ext.fi: MKL-style API interfaces (LP64,MKL_INTasc_int)include/cqr_mkl_ext_ilp64.fi: ILP64 variant with 64-bitMKL_INT(c_long_long)include/cqr_mkl_enums.fi: Shared MKL enumerators (layout, uplo, side, trans, diag, pack format)Dual-Form Layout Convention:
.fifiles use a portable column layout readable as both fixed-form (72-column) and free-form Fortran&(free-form marker) continued by column-6&(fixed-form marker)Precision-Generic Interfaces:
cqr_mkl_geqrf_compact)Test Suite:
tests/test_cqr_fortran_compact.F90: Free-form test of portable APItests/test_cqr_fortran_compact_fixed.f: Fixed-form proof that.fifiles read as fixed-formtests/test_cqr_fortran_mkl.F90: Free-form test of MKL-style API (LP64 and ILP64 variants)tests/test_cqr_fortran_mkl_fixed.F: Fixed-form proof for MKL-style APItests/test_cqr_fortran_util.inc: Shared scaffolding (fill, pack, check utilities)tests/check_fi_twins.py: Verification that LP64 and ILP64 interface files differ only in integer kindsBuild Integration:
-DCQR_BUILD_FORTRAN_TESTS=ON(opt-in, requires Fortran compiler)Documentation:
README.mdand.claude/CLAUDE.mdwith Fortran interface informationImplementation Details
Workspace Contract: Like MKL's own compact routines, the Fortran interfaces skip argument checking. Each routine sizes its work via
lwork = -1query; undersized or shared work arrays cause silent overruns (documented incqr_mkl_ext.h).Enumerator Mapping: MKL enumerators (C
intenums) are transcribed as Fortranenum, bind(c)blocks with identical values from MKL 2020.0.4 and oneMKL 2026.1.ILP64 Support:
https://claude.ai/code/session_01PEJMMExeRTHLcTZ9dopKWq