Skip to main content

sparse_ir_capi/
sampling.rs

1//! Sampling API for C
2//!
3//! This module provides the C API for sparse sampling in imaginary time (τ),
4//! Matsubara frequency (iωn), and real frequency (ω) domains.
5//!
6//! Functions:
7//! - Creation: spir_tau_sampling_new, spir_matsu_sampling_new, ...
8//! - Introspection: get_npoints, get_taus, get_matsus, get_cond_num
9//! - Evaluation: eval_dd, eval_dz, eval_zz (coefficients → sampling points)
10//! - Fitting: fit_dd, fit_zz, fit_zd (sampling points → coefficients)
11//! - Memory: release, clone, is_assigned (via macro)
12
13use mdarray::Shape;
14use num_complex::Complex64;
15use std::panic::{AssertUnwindSafe, catch_unwind};
16use std::sync::Arc;
17
18use crate::gemm::{get_backend_handle, spir_gemm_backend};
19use crate::types::{BasisType, SamplingType, spir_basis, spir_sampling};
20use crate::utils::{
21    MemoryOrder, build_output_dims, convert_dims_for_row_major, create_dview_from_ptr,
22    create_dviewmut_from_ptr, read_tensor_nd,
23};
24use crate::{
25    SPIR_COMPUTATION_SUCCESS, SPIR_INVALID_ARGUMENT, SPIR_NOT_SUPPORTED, SPIR_STATISTICS_BOSONIC,
26    SPIR_STATISTICS_FERMIONIC, StatusCode,
27};
28use sparse_ir::fitters::InplaceFitter;
29use sparse_ir::{Bosonic, Fermionic};
30
31/// Manual release function (replaces macro-generated one)
32#[unsafe(no_mangle)]
33pub extern "C" fn spir_sampling_release(sampling: *mut spir_sampling) {
34    if !sampling.is_null() {
35        unsafe {
36            let _ = Box::from_raw(sampling);
37        }
38    }
39}
40
41/// Manual clone function (replaces macro-generated one)
42#[unsafe(no_mangle)]
43pub extern "C" fn spir_sampling_clone(src: *const spir_sampling) -> *mut spir_sampling {
44    if src.is_null() {
45        return std::ptr::null_mut();
46    }
47
48    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
49        let src_ref = &*src;
50        let cloned = (*src_ref).clone();
51        Box::into_raw(Box::new(cloned))
52    }));
53
54    result.unwrap_or(std::ptr::null_mut())
55}
56
57/// Check if the sampling pointer is non-null.
58///
59/// Note: This only performs a null check. It cannot detect dangling
60/// pointers; dereferencing an arbitrary non-null pointer would be
61/// undefined behaviour that `catch_unwind` cannot reliably catch.
62///
63/// # Returns
64/// 1 if the pointer is non-null, 0 otherwise
65#[unsafe(no_mangle)]
66pub extern "C" fn spir_sampling_is_assigned(obj: *const spir_sampling) -> i32 {
67    if obj.is_null() { 0 } else { 1 }
68}
69
70// ============================================================================
71// Creation Functions
72// ============================================================================
73
74/// Creates a new tau sampling object for sparse sampling in imaginary time
75///
76/// # Arguments
77/// * `b` - Pointer to a finite temperature basis object
78/// * `num_points` - Number of sampling points
79/// * `points` - Array of sampling points in imaginary time (τ)
80/// * `status` - Pointer to store the status code
81///
82/// # Returns
83/// Pointer to the newly created sampling object, or NULL if creation fails
84///
85/// # Safety
86/// Caller must ensure `b` is valid and `points` has `num_points` elements
87#[unsafe(no_mangle)]
88pub extern "C" fn spir_tau_sampling_new(
89    b: *const spir_basis,
90    num_points: libc::c_int,
91    points: *const f64,
92    status: *mut StatusCode,
93) -> *mut spir_sampling {
94    let result = catch_unwind(AssertUnwindSafe(|| {
95        // Validate inputs
96        if b.is_null() || points.is_null() {
97            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
98        }
99        if num_points <= 0 {
100            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
101        }
102
103        let basis_ref = unsafe { &*b };
104        let points_slice = unsafe { std::slice::from_raw_parts(points, num_points as usize) };
105
106        // Convert points to Vec
107        let tau_points: Vec<f64> = points_slice.to_vec();
108
109        // Create sampling based on basis statistics
110        let sampling_type = match basis_ref.inner() {
111            BasisType::LogisticFermionic(ir_basis) => {
112                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
113                    ir_basis.as_ref(),
114                    tau_points,
115                );
116                SamplingType::TauFermionic(Arc::new(tau_sampling))
117            }
118            BasisType::RegularizedBoseFermionic(ir_basis) => {
119                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
120                    ir_basis.as_ref(),
121                    tau_points,
122                );
123                SamplingType::TauFermionic(Arc::new(tau_sampling))
124            }
125            BasisType::LogisticBosonic(ir_basis) => {
126                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
127                    ir_basis.as_ref(),
128                    tau_points,
129                );
130                SamplingType::TauBosonic(Arc::new(tau_sampling))
131            }
132            BasisType::RegularizedBoseBosonic(ir_basis) => {
133                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
134                    ir_basis.as_ref(),
135                    tau_points,
136                );
137                SamplingType::TauBosonic(Arc::new(tau_sampling))
138            }
139            // DLR: tau sampling supported via Basis trait
140            BasisType::DLRFermionic(dlr) => {
141                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
142                    dlr.as_ref(),
143                    tau_points,
144                );
145                SamplingType::TauFermionic(Arc::new(tau_sampling))
146            }
147            BasisType::DLRBosonic(dlr) => {
148                let tau_sampling = sparse_ir::sampling::TauSampling::with_sampling_points(
149                    dlr.as_ref(),
150                    tau_points,
151                );
152                SamplingType::TauBosonic(Arc::new(tau_sampling))
153            }
154        };
155
156        let inner = sampling_type;
157        let sampling = spir_sampling {
158            _private: Box::into_raw(Box::new(inner)) as *mut std::ffi::c_void,
159        };
160
161        (Box::into_raw(Box::new(sampling)), SPIR_COMPUTATION_SUCCESS)
162    }));
163
164    match result {
165        Ok((ptr, code)) => {
166            if !status.is_null() {
167                unsafe {
168                    *status = code;
169                }
170            }
171            ptr
172        }
173        Err(_) => {
174            if !status.is_null() {
175                unsafe {
176                    *status = crate::SPIR_INTERNAL_ERROR;
177                }
178            }
179            std::ptr::null_mut()
180        }
181    }
182}
183
184/// Creates a new Matsubara sampling object for sparse sampling in Matsubara frequencies
185///
186/// # Arguments
187/// * `b` - Pointer to a finite temperature basis object
188/// * `positive_only` - If true, only positive frequencies are used
189/// * `num_points` - Number of sampling points
190/// * `points` - Array of Matsubara frequency indices (n)
191/// * `status` - Pointer to store the status code
192///
193/// # Returns
194/// Pointer to the newly created sampling object, or NULL if creation fails
195#[unsafe(no_mangle)]
196pub extern "C" fn spir_matsu_sampling_new(
197    b: *const spir_basis,
198    positive_only: bool,
199    num_points: libc::c_int,
200    points: *const i64,
201    status: *mut StatusCode,
202) -> *mut spir_sampling {
203    let result = catch_unwind(AssertUnwindSafe(|| {
204        // Validate inputs
205        if b.is_null() || points.is_null() {
206            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
207        }
208        if num_points <= 0 {
209            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
210        }
211
212        let basis_ref = unsafe { &*b };
213        let points_slice = unsafe { std::slice::from_raw_parts(points, num_points as usize) };
214
215        // Convert points to Vec
216        let matsu_points: Vec<i64> = points_slice.to_vec();
217
218        // Convert i64 indices to MatsubaraFreq
219        use sparse_ir::freq::MatsubaraFreq;
220
221        // Helper macro to reduce duplication
222        macro_rules! create_matsu_sampling {
223            ($basis:expr, Fermionic) => {
224                if positive_only {
225                    let matsu_freqs: Vec<MatsubaraFreq<Fermionic>> = matsu_points
226                        .iter()
227                        .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
228                        .collect();
229                    let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSamplingPositiveOnly::with_sampling_points(
230                        $basis,
231                        matsu_freqs,
232                    );
233                    SamplingType::MatsubaraPositiveOnlyFermionic(Arc::new(matsu_sampling))
234                } else {
235                    let matsu_freqs: Vec<MatsubaraFreq<Fermionic>> = matsu_points
236                        .iter()
237                        .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
238                        .collect();
239                    let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSampling::with_sampling_points(
240                        $basis,
241                        matsu_freqs,
242                    );
243                    SamplingType::MatsubaraFermionic(Arc::new(matsu_sampling))
244                }
245            };
246            ($basis:expr, Bosonic) => {
247                if positive_only {
248                    let matsu_freqs: Vec<MatsubaraFreq<Bosonic>> = matsu_points
249                        .iter()
250                        .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
251                        .collect();
252                    let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSamplingPositiveOnly::with_sampling_points(
253                        $basis,
254                        matsu_freqs,
255                    );
256                    SamplingType::MatsubaraPositiveOnlyBosonic(Arc::new(matsu_sampling))
257                } else {
258                    let matsu_freqs: Vec<MatsubaraFreq<Bosonic>> = matsu_points
259                        .iter()
260                        .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
261                        .collect();
262                    let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSampling::with_sampling_points(
263                        $basis,
264                        matsu_freqs,
265                    );
266                    SamplingType::MatsubaraBosonic(Arc::new(matsu_sampling))
267                }
268            };
269        }
270
271        // Create sampling based on basis statistics and positive_only flag
272        let sampling_type = match basis_ref.inner() {
273            BasisType::LogisticFermionic(ir_basis) => {
274                create_matsu_sampling!(ir_basis.as_ref(), Fermionic)
275            }
276            BasisType::RegularizedBoseFermionic(ir_basis) => {
277                create_matsu_sampling!(ir_basis.as_ref(), Fermionic)
278            }
279            BasisType::LogisticBosonic(ir_basis) => {
280                create_matsu_sampling!(ir_basis.as_ref(), Bosonic)
281            }
282            BasisType::RegularizedBoseBosonic(ir_basis) => {
283                create_matsu_sampling!(ir_basis.as_ref(), Bosonic)
284            }
285            // DLR: Matsubara sampling supported via Basis trait
286            BasisType::DLRFermionic(dlr) => {
287                create_matsu_sampling!(dlr.as_ref(), Fermionic)
288            }
289            BasisType::DLRBosonic(dlr) => {
290                create_matsu_sampling!(dlr.as_ref(), Bosonic)
291            }
292        };
293
294        let inner = sampling_type;
295        let sampling = spir_sampling {
296            _private: Box::into_raw(Box::new(inner)) as *mut std::ffi::c_void,
297        };
298
299        (Box::into_raw(Box::new(sampling)), SPIR_COMPUTATION_SUCCESS)
300    }));
301
302    match result {
303        Ok((ptr, code)) => {
304            if !status.is_null() {
305                unsafe {
306                    *status = code;
307                }
308            }
309            ptr
310        }
311        Err(_) => {
312            if !status.is_null() {
313                unsafe {
314                    *status = crate::SPIR_INTERNAL_ERROR;
315                }
316            }
317            std::ptr::null_mut()
318        }
319    }
320}
321
322/// Creates a new tau sampling object with custom sampling points and pre-computed matrix
323///
324/// # Arguments
325/// * `order` - Memory layout order (SPIR_ORDER_ROW_MAJOR or SPIR_ORDER_COLUMN_MAJOR)
326/// * `statistics` - Statistics type (SPIR_STATISTICS_FERMIONIC or SPIR_STATISTICS_BOSONIC)
327/// * `basis_size` - Basis size
328/// * `num_points` - Number of sampling points
329/// * `points` - Array of sampling points in imaginary time (τ)
330/// * `matrix` - Pre-computed matrix for the sampling points (num_points x basis_size)
331/// * `status` - Pointer to store the status code
332///
333/// # Returns
334/// Pointer to the newly created sampling object, or NULL if creation fails
335///
336/// # Safety
337/// Caller must ensure `points` and `matrix` have correct sizes
338#[unsafe(no_mangle)]
339pub extern "C" fn spir_tau_sampling_new_with_matrix(
340    order: libc::c_int,
341    statistics: libc::c_int,
342    basis_size: libc::c_int,
343    num_points: libc::c_int,
344    points: *const f64,
345    matrix: *const f64,
346    status: *mut StatusCode,
347) -> *mut spir_sampling {
348    let result = catch_unwind(AssertUnwindSafe(|| {
349        // Validate inputs
350        if points.is_null() || matrix.is_null() {
351            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
352        }
353        if num_points <= 0 || basis_size <= 0 {
354            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
355        }
356
357        // Parse order
358        let mem_order = match MemoryOrder::from_c_int(order) {
359            Ok(o) => o,
360            Err(_) => return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT),
361        };
362
363        // Convert points to Vec
364        let points_slice = unsafe { std::slice::from_raw_parts(points, num_points as usize) };
365        let tau_points: Vec<f64> = points_slice.to_vec();
366
367        // Convert matrix to Tensor using the new helper function
368        let orig_dims = [num_points as usize, basis_size as usize];
369        let dyn_tensor = unsafe { read_tensor_nd(matrix, &orig_dims, mem_order) };
370
371        // Convert DynRank to fixed 2D shape using from_fn (safe conversion)
372        let shape_dims = dyn_tensor.shape().with_dims(|dims| dims.to_vec());
373        assert_eq!(
374            shape_dims.len(),
375            2,
376            "Expected 2D tensor, got {}D",
377            shape_dims.len()
378        );
379        let num_points_actual = shape_dims[0];
380        let basis_size_actual = shape_dims[1];
381        let matrix_tensor =
382            sparse_ir::DTensor::<f64, 2>::from_fn([num_points_actual, basis_size_actual], |idx| {
383                dyn_tensor[&[idx[0], idx[1]][..]]
384            });
385        // Create sampling based on statistics
386        let sampling_type = match statistics {
387            SPIR_STATISTICS_FERMIONIC => {
388                // SPIR_STATISTICS_FERMIONIC
389                let tau_sampling = sparse_ir::sampling::TauSampling::<Fermionic>::from_matrix(
390                    tau_points,
391                    matrix_tensor,
392                );
393                SamplingType::TauFermionic(Arc::new(tau_sampling))
394            }
395            SPIR_STATISTICS_BOSONIC => {
396                // SPIR_STATISTICS_BOSONIC
397                let tau_sampling = sparse_ir::sampling::TauSampling::<Bosonic>::from_matrix(
398                    tau_points,
399                    matrix_tensor,
400                );
401                SamplingType::TauBosonic(Arc::new(tau_sampling))
402            }
403            _ => return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT),
404        };
405
406        let inner = sampling_type;
407        let sampling = spir_sampling {
408            _private: Box::into_raw(Box::new(inner)) as *mut std::ffi::c_void,
409        };
410
411        (Box::into_raw(Box::new(sampling)), SPIR_COMPUTATION_SUCCESS)
412    }));
413
414    match result {
415        Ok((ptr, code)) => {
416            if !status.is_null() {
417                unsafe {
418                    *status = code;
419                }
420            }
421            ptr
422        }
423        Err(_) => {
424            if !status.is_null() {
425                unsafe {
426                    *status = crate::SPIR_INTERNAL_ERROR;
427                }
428            }
429            std::ptr::null_mut()
430        }
431    }
432}
433
434/// Creates a new Matsubara sampling object with custom sampling points and pre-computed matrix
435///
436/// # Arguments
437/// * `order` - Memory layout order (SPIR_ORDER_ROW_MAJOR or SPIR_ORDER_COLUMN_MAJOR)
438/// * `statistics` - Statistics type (SPIR_STATISTICS_FERMIONIC or SPIR_STATISTICS_BOSONIC)
439/// * `basis_size` - Basis size
440/// * `positive_only` - If true, only positive frequencies are used
441/// * `num_points` - Number of sampling points
442/// * `points` - Array of Matsubara frequency indices (n)
443/// * `matrix` - Pre-computed complex matrix (num_points x basis_size)
444/// * `status` - Pointer to store the status code
445///
446/// # Returns
447/// Pointer to the newly created sampling object, or NULL if creation fails
448///
449/// # Safety
450/// Caller must ensure `points` and `matrix` have correct sizes
451#[unsafe(no_mangle)]
452pub extern "C" fn spir_matsu_sampling_new_with_matrix(
453    order: libc::c_int,
454    statistics: libc::c_int,
455    basis_size: libc::c_int,
456    positive_only: bool,
457    num_points: libc::c_int,
458    points: *const i64,
459    matrix: *const Complex64,
460    status: *mut StatusCode,
461) -> *mut spir_sampling {
462    use std::io::Write;
463    debug_println!(
464        "spir_matsu_sampling_new_with_matrix: start, order={}, statistics={}, basis_size={}, positive_only={}, num_points={}",
465        order,
466        statistics,
467        basis_size,
468        positive_only,
469        num_points
470    );
471    std::io::stderr().flush().ok();
472    let result = catch_unwind(AssertUnwindSafe(|| {
473        use std::io::Write;
474        debug_println!("spir_matsu_sampling_new_with_matrix: inside catch_unwind");
475        std::io::stderr().flush().ok();
476        // Validate inputs
477        if points.is_null() || matrix.is_null() {
478            debug_eprintln!("spir_matsu_sampling_new_with_matrix: null pointer");
479            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
480        }
481        if num_points <= 0 || basis_size <= 0 {
482            debug_eprintln!(
483                "spir_matsu_sampling_new_with_matrix: invalid size, num_points={}, basis_size={}",
484                num_points,
485                basis_size
486            );
487            return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT);
488        }
489        debug_println!("spir_matsu_sampling_new_with_matrix: input validation passed");
490        std::io::stderr().flush().ok();
491
492        // Parse order
493        let mem_order = match MemoryOrder::from_c_int(order) {
494            Ok(o) => o,
495            Err(_) => return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT),
496        };
497
498        // Convert points to Vec<MatsubaraFreq>
499        debug_println!("spir_matsu_sampling_new_with_matrix: creating points slice...");
500        std::io::stderr().flush().ok();
501        let points_slice = unsafe { std::slice::from_raw_parts(points, num_points as usize) };
502        debug_println!(
503            "spir_matsu_sampling_new_with_matrix: points slice created, len = {}",
504            points_slice.len()
505        );
506        std::io::stderr().flush().ok();
507        let matsu_points: Vec<i64> = points_slice.to_vec();
508        debug_println!(
509            "spir_matsu_sampling_new_with_matrix: matsu_points created, len = {}",
510            matsu_points.len()
511        );
512        std::io::stderr().flush().ok();
513
514        use sparse_ir::freq::MatsubaraFreq;
515
516        // Convert matrix to Tensor using the new helper function
517        let orig_dims = [num_points as usize, basis_size as usize];
518        debug_println!(
519            "spir_matsu_sampling_new_with_matrix: orig_dims = {:?}, mem_order = {:?}",
520            orig_dims,
521            mem_order
522        );
523        std::io::stderr().flush().ok();
524
525        debug_println!("spir_matsu_sampling_new_with_matrix: reading tensor from buffer...");
526        std::io::stderr().flush().ok();
527        let dyn_tensor = unsafe { read_tensor_nd(matrix, &orig_dims, mem_order) };
528        let shape_dims = dyn_tensor.shape().with_dims(|dims| dims.to_vec());
529        debug_println!(
530            "spir_matsu_sampling_new_with_matrix: dyn_tensor created, shape = {:?}",
531            shape_dims
532        );
533        std::io::stderr().flush().ok();
534
535        // Convert DynRank to fixed 2D shape using from_fn (safe conversion)
536        debug_println!("spir_matsu_sampling_new_with_matrix: converting to fixed 2D tensor...");
537        std::io::stderr().flush().ok();
538        assert_eq!(
539            shape_dims.len(),
540            2,
541            "Expected 2D tensor, got {}D",
542            shape_dims.len()
543        );
544        let num_points_actual = shape_dims[0];
545        let basis_size_actual = shape_dims[1];
546        debug_println!(
547            "spir_matsu_sampling_new_with_matrix: converting from shape {:?} to DTensor<Complex64, 2>",
548            shape_dims
549        );
550        std::io::stderr().flush().ok();
551        let matrix_tensor = sparse_ir::DTensor::<Complex64, 2>::from_fn(
552            [num_points_actual, basis_size_actual],
553            |idx| dyn_tensor[&[idx[0], idx[1]][..]],
554        );
555        debug_println!(
556            "spir_matsu_sampling_new_with_matrix: matrix_tensor created, shape = {:?}",
557            matrix_tensor.shape()
558        );
559        std::io::stderr().flush().ok();
560
561        // Create sampling based on statistics and positive_only
562        debug_println!(
563            "spir_matsu_sampling_new_with_matrix: creating sampling, statistics={}, positive_only={}",
564            statistics,
565            positive_only
566        );
567        std::io::stderr().flush().ok();
568        let sampling_type = match (statistics, positive_only) {
569            (SPIR_STATISTICS_FERMIONIC, true) => {
570                debug_println!("spir_matsu_sampling_new_with_matrix: Fermionic, positive-only");
571                std::io::stderr().flush().ok();
572                // Fermionic, positive-only
573                let matsu_freqs: Vec<MatsubaraFreq<Fermionic>> = matsu_points
574                    .iter()
575                    .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
576                    .collect();
577                debug_println!(
578                    "spir_matsu_sampling_new_with_matrix: matsu_freqs created, len = {}",
579                    matsu_freqs.len()
580                );
581                std::io::stderr().flush().ok();
582                debug_println!("spir_matsu_sampling_new_with_matrix: calling from_matrix...");
583                std::io::stderr().flush().ok();
584                let matsu_sampling =
585                    sparse_ir::matsubara_sampling::MatsubaraSamplingPositiveOnly::from_matrix(
586                        matsu_freqs,
587                        matrix_tensor.clone(),
588                    );
589                debug_println!("spir_matsu_sampling_new_with_matrix: from_matrix returned");
590                std::io::stderr().flush().ok();
591                SamplingType::MatsubaraPositiveOnlyFermionic(Arc::new(matsu_sampling))
592            }
593            (SPIR_STATISTICS_FERMIONIC, false) => {
594                debug_println!("spir_matsu_sampling_new_with_matrix: Fermionic, full range");
595                std::io::stderr().flush().ok();
596                // Fermionic, full range
597                let matsu_freqs: Vec<MatsubaraFreq<Fermionic>> = matsu_points
598                    .iter()
599                    .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
600                    .collect();
601                debug_println!(
602                    "spir_matsu_sampling_new_with_matrix: matsu_freqs created, len = {}",
603                    matsu_freqs.len()
604                );
605                std::io::stderr().flush().ok();
606                debug_println!("spir_matsu_sampling_new_with_matrix: calling from_matrix...");
607                std::io::stderr().flush().ok();
608                let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSampling::from_matrix(
609                    matsu_freqs,
610                    matrix_tensor.clone(),
611                );
612                debug_println!("spir_matsu_sampling_new_with_matrix: from_matrix returned");
613                std::io::stderr().flush().ok();
614                SamplingType::MatsubaraFermionic(Arc::new(matsu_sampling))
615            }
616            (SPIR_STATISTICS_BOSONIC, true) => {
617                // Bosonic, positive-only
618                let matsu_freqs: Vec<MatsubaraFreq<Bosonic>> = matsu_points
619                    .iter()
620                    .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
621                    .collect();
622                let matsu_sampling =
623                    sparse_ir::matsubara_sampling::MatsubaraSamplingPositiveOnly::from_matrix(
624                        matsu_freqs,
625                        matrix_tensor.clone(),
626                    );
627                SamplingType::MatsubaraPositiveOnlyBosonic(Arc::new(matsu_sampling))
628            }
629            (SPIR_STATISTICS_BOSONIC, false) => {
630                // Bosonic, full range
631                let matsu_freqs: Vec<MatsubaraFreq<Bosonic>> = matsu_points
632                    .iter()
633                    .map(|&n| MatsubaraFreq::new(n).expect("Invalid Matsubara frequency"))
634                    .collect();
635                let matsu_sampling = sparse_ir::matsubara_sampling::MatsubaraSampling::from_matrix(
636                    matsu_freqs,
637                    matrix_tensor.clone(),
638                );
639                SamplingType::MatsubaraBosonic(Arc::new(matsu_sampling))
640            }
641            _ => return (std::ptr::null_mut(), SPIR_INVALID_ARGUMENT),
642        };
643
644        let inner = sampling_type;
645        let sampling = spir_sampling {
646            _private: Box::into_raw(Box::new(inner)) as *mut std::ffi::c_void,
647        };
648
649        (Box::into_raw(Box::new(sampling)), SPIR_COMPUTATION_SUCCESS)
650    }));
651
652    match result {
653        Ok((ptr, code)) => {
654            if !status.is_null() {
655                unsafe {
656                    *status = code;
657                }
658            }
659            ptr
660        }
661        Err(_) => {
662            if !status.is_null() {
663                unsafe {
664                    *status = crate::SPIR_INTERNAL_ERROR;
665                }
666            }
667            std::ptr::null_mut()
668        }
669    }
670}
671
672// ============================================================================
673// Introspection Functions
674// ============================================================================
675
676/// Gets the number of sampling points in a sampling object.
677///
678/// This function returns the number of sampling points used in the specified
679/// sampling object. This number is needed to allocate arrays of the correct size
680/// when retrieving the actual sampling points.
681///
682/// # Arguments
683///
684/// * `s` - Pointer to the sampling object.
685/// * `num_points` - Pointer to store the number of sampling points.
686///
687/// # Returns
688///
689/// A status code:
690/// - `0` ([`SPIR_COMPUTATION_SUCCESS`]) on success
691/// - A non-zero error code on failure
692///
693/// # See also
694///
695/// - [`spir_sampling_get_taus`]
696/// - [`spir_sampling_get_matsus`]
697#[unsafe(no_mangle)]
698pub extern "C" fn spir_sampling_get_npoints(
699    s: *const spir_sampling,
700    num_points: *mut libc::c_int,
701) -> StatusCode {
702    let result = catch_unwind(AssertUnwindSafe(|| {
703        if s.is_null() || num_points.is_null() {
704            return SPIR_INVALID_ARGUMENT;
705        }
706
707        let sampling_ref = unsafe { &*s };
708
709        let n_points = match sampling_ref.inner() {
710            SamplingType::TauFermionic(tau) => tau.n_sampling_points(),
711            SamplingType::TauBosonic(tau) => tau.n_sampling_points(),
712            SamplingType::MatsubaraFermionic(matsu) => matsu.n_sampling_points(),
713            SamplingType::MatsubaraBosonic(matsu) => matsu.n_sampling_points(),
714            SamplingType::MatsubaraPositiveOnlyFermionic(matsu) => matsu.n_sampling_points(),
715            SamplingType::MatsubaraPositiveOnlyBosonic(matsu) => matsu.n_sampling_points(),
716        };
717
718        unsafe {
719            *num_points = n_points as libc::c_int;
720        }
721        SPIR_COMPUTATION_SUCCESS
722    }));
723
724    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
725}
726
727/// Gets the imaginary time (τ) sampling points used in the specified sampling object.
728///
729/// This function fills the provided array with the imaginary time (τ) sampling points used in the specified sampling object.
730/// The array must be pre-allocated with sufficient size (use [`spir_sampling_get_npoints`] to determine the required size).
731///
732/// # Arguments
733///
734/// * `s` - Pointer to the sampling object.
735/// * `points` - Pre-allocated array to store the τ sampling points.
736///
737/// # Returns
738///
739/// An integer status code:
740/// - `0` ([`SPIR_COMPUTATION_SUCCESS`]) on success
741/// - A non-zero error code on failure
742///
743/// # Notes
744///
745/// The array must be pre-allocated with size >= [`spir_sampling_get_npoints`](spir_sampling_get_npoints).
746///
747/// # See also
748///
749/// - [`spir_sampling_get_npoints`]
750#[unsafe(no_mangle)]
751pub extern "C" fn spir_sampling_get_taus(s: *const spir_sampling, points: *mut f64) -> StatusCode {
752    let result = catch_unwind(AssertUnwindSafe(|| {
753        if s.is_null() || points.is_null() {
754            return SPIR_INVALID_ARGUMENT;
755        }
756
757        let sampling_ref = unsafe { &*s };
758
759        match sampling_ref.inner() {
760            SamplingType::TauFermionic(tau) => {
761                let tau_points = tau.sampling_points();
762                let out_slice = unsafe { std::slice::from_raw_parts_mut(points, tau_points.len()) };
763                out_slice.copy_from_slice(tau_points);
764                SPIR_COMPUTATION_SUCCESS
765            }
766            SamplingType::TauBosonic(tau) => {
767                let tau_points = tau.sampling_points();
768                let out_slice = unsafe { std::slice::from_raw_parts_mut(points, tau_points.len()) };
769                out_slice.copy_from_slice(tau_points);
770                SPIR_COMPUTATION_SUCCESS
771            }
772            _ => SPIR_NOT_SUPPORTED,
773        }
774    }));
775
776    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
777}
778
779/// Gets the Matsubara frequency sampling points
780#[unsafe(no_mangle)]
781pub extern "C" fn spir_sampling_get_matsus(
782    s: *const spir_sampling,
783    points: *mut i64,
784) -> StatusCode {
785    let result = catch_unwind(AssertUnwindSafe(|| {
786        if s.is_null() || points.is_null() {
787            return SPIR_INVALID_ARGUMENT;
788        }
789
790        let sampling_ref = unsafe { &*s };
791
792        match sampling_ref.inner() {
793            SamplingType::MatsubaraFermionic(matsu) => {
794                let matsu_freqs = matsu.sampling_points();
795                let out_slice =
796                    unsafe { std::slice::from_raw_parts_mut(points, matsu_freqs.len()) };
797                for (i, freq) in matsu_freqs.iter().enumerate() {
798                    out_slice[i] = freq.n();
799                }
800                SPIR_COMPUTATION_SUCCESS
801            }
802            SamplingType::MatsubaraBosonic(matsu) => {
803                let matsu_freqs = matsu.sampling_points();
804                let out_slice =
805                    unsafe { std::slice::from_raw_parts_mut(points, matsu_freqs.len()) };
806                for (i, freq) in matsu_freqs.iter().enumerate() {
807                    out_slice[i] = freq.n();
808                }
809                SPIR_COMPUTATION_SUCCESS
810            }
811            SamplingType::MatsubaraPositiveOnlyFermionic(matsu) => {
812                let matsu_freqs = matsu.sampling_points();
813                let out_slice =
814                    unsafe { std::slice::from_raw_parts_mut(points, matsu_freqs.len()) };
815                for (i, freq) in matsu_freqs.iter().enumerate() {
816                    out_slice[i] = freq.n();
817                }
818                SPIR_COMPUTATION_SUCCESS
819            }
820            SamplingType::MatsubaraPositiveOnlyBosonic(matsu) => {
821                let matsu_freqs = matsu.sampling_points();
822                let out_slice =
823                    unsafe { std::slice::from_raw_parts_mut(points, matsu_freqs.len()) };
824                for (i, freq) in matsu_freqs.iter().enumerate() {
825                    out_slice[i] = freq.n();
826                }
827                SPIR_COMPUTATION_SUCCESS
828            }
829            _ => SPIR_NOT_SUPPORTED,
830        }
831    }));
832
833    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
834}
835
836/// Gets the condition number of the sampling matrix.
837///
838/// This function returns the condition number of the sampling matrix used in the
839/// specified sampling object. The condition number is a measure of how well-
840/// conditioned the sampling matrix is.
841///
842/// # Parameters
843/// - `s`: Pointer to the sampling object.
844/// - `cond_num`: Pointer to store the condition number.
845///
846/// # Returns
847/// An integer status code:
848/// - 0 (`SPIR_COMPUTATION_SUCCESS`) on success
849/// - Non-zero error code on failure
850///
851/// # Notes
852/// - A large condition number indicates that the sampling matrix is ill-conditioned,
853///   which may lead to numerical instability in transformations.
854/// - The condition number is the ratio of the largest to smallest singular value
855///   of the sampling matrix.
856#[unsafe(no_mangle)]
857pub extern "C" fn spir_sampling_get_cond_num(
858    s: *const spir_sampling,
859    cond_num: *mut f64,
860) -> StatusCode {
861    let result = catch_unwind(AssertUnwindSafe(|| {
862        if s.is_null() || cond_num.is_null() {
863            return SPIR_INVALID_ARGUMENT;
864        }
865
866        let sampling_ref = unsafe { &*s };
867
868        // Calculate condition number from SVD of the sampling matrix
869        let condition_number = match sampling_ref.inner() {
870            SamplingType::TauFermionic(tau) => {
871                // For tau sampling, matrix is real
872                let matrix = tau.matrix();
873                compute_condition_number_real(matrix)
874            }
875            SamplingType::TauBosonic(tau) => {
876                // For tau sampling, matrix is real
877                let matrix = tau.matrix();
878                compute_condition_number_real(matrix)
879            }
880            SamplingType::MatsubaraFermionic(matsu) => {
881                // For Matsubara sampling, matrix is complex
882                let matrix = matsu.matrix();
883                compute_condition_number_complex(matrix)
884            }
885            SamplingType::MatsubaraBosonic(matsu) => {
886                let matrix = matsu.matrix();
887                compute_condition_number_complex(matrix)
888            }
889            SamplingType::MatsubaraPositiveOnlyFermionic(matsu) => {
890                // For positive-only Matsubara, use the complex matrix
891                // The fitter uses ComplexToRealFitter internally, but we can use the complex matrix
892                let matrix = matsu.matrix();
893                compute_condition_number_complex(matrix)
894            }
895            SamplingType::MatsubaraPositiveOnlyBosonic(matsu) => {
896                let matrix = matsu.matrix();
897                compute_condition_number_complex(matrix)
898            }
899        };
900
901        unsafe {
902            *cond_num = condition_number;
903        }
904        SPIR_COMPUTATION_SUCCESS
905    }));
906
907    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
908}
909
910/// Compute condition number from real matrix using SVD
911fn compute_condition_number_real(matrix: &mdarray::DTensor<f64, 2>) -> f64 {
912    use mdarray_linalg::prelude::SVD;
913    use mdarray_linalg::svd::SVDDecomp;
914    use mdarray_linalg_faer::Faer;
915
916    let mut matrix_copy = matrix.clone();
917    let SVDDecomp { s, .. } = Faer.svd(&mut *matrix_copy).expect("SVD computation failed");
918
919    let min_dim = s.shape().0.min(s.shape().1);
920    if min_dim == 0 {
921        return 1.0;
922    }
923
924    let max_sv = s[[0, 0]];
925    let min_sv = s[[0, min_dim - 1]];
926
927    if min_sv.abs() < 1e-15 {
928        // Matrix is singular or nearly singular
929        return f64::INFINITY;
930    }
931
932    max_sv / min_sv
933}
934
935/// Compute condition number from complex matrix using SVD
936fn compute_condition_number_complex(matrix: &mdarray::DTensor<num_complex::Complex64, 2>) -> f64 {
937    use mdarray_linalg::prelude::SVD;
938    use mdarray_linalg::svd::SVDDecomp;
939    use mdarray_linalg_faer::Faer;
940
941    let mut matrix_copy = matrix.clone();
942    let SVDDecomp { s, .. } = Faer.svd(&mut *matrix_copy).expect("SVD computation failed");
943
944    let min_dim = s.shape().0.min(s.shape().1);
945    if min_dim == 0 {
946        return 1.0;
947    }
948
949    // Singular values are real (stored as Complex, but imaginary part is 0)
950    let max_sv = s[[0, 0]].re;
951    let min_sv = s[[0, min_dim - 1]].re;
952
953    if min_sv.abs() < 1e-15 {
954        // Matrix is singular or nearly singular
955        return f64::INFINITY;
956    }
957
958    max_sv / min_sv
959}
960
961// ============================================================================
962// Evaluation Functions (coefficients → sampling points)
963// ============================================================================
964
965/// Evaluates basis coefficients at sampling points (double to double version).
966///
967/// Transforms basis coefficients to values at sampling points, where both input
968/// and output are real (double precision) values. The operation can be performed
969/// along any dimension of a multidimensional array.
970///
971/// # Arguments
972///
973/// * `s` - Pointer to the sampling object
974/// * `order` - Memory layout order (`SPIR_ORDER_ROW_MAJOR` or `SPIR_ORDER_COLUMN_MAJOR`)
975/// * `ndim` - Number of dimensions in the input/output arrays
976/// * `input_dims` - Array of dimension sizes
977/// * `target_dim` - Target dimension for the transformation (0-based)
978/// * `input` - Input array of basis coefficients
979/// * `out` - Output array for the evaluated values at sampling points
980///
981/// # Returns
982///
983/// An integer status code:
984/// - `0` (`SPIR_COMPUTATION_SUCCESS`) on success
985/// - A non-zero error code on failure
986///
987/// # Notes
988///
989/// - For optimal performance, the target dimension should be either the
990///   first (`0`) or the last (`ndim-1`) dimension to avoid large temporary array allocations
991/// - The output array must be pre-allocated with the correct size
992/// - The input and output arrays must be contiguous in memory
993/// - The transformation is performed using a pre-computed sampling matrix
994///   that is factorized using SVD for efficiency
995///
996/// # See also
997/// - [`spir_sampling_eval_dz`]
998/// - [`spir_sampling_eval_zz`]
999/// # Note
1000/// Supports both row-major and column-major order. Zero-copy implementation.
1001#[unsafe(no_mangle)]
1002pub extern "C" fn spir_sampling_eval_dd(
1003    s: *const spir_sampling,
1004    backend: *const spir_gemm_backend,
1005    order: libc::c_int,
1006    ndim: libc::c_int,
1007    input_dims: *const libc::c_int,
1008    target_dim: libc::c_int,
1009    input: *const f64,
1010    out: *mut f64,
1011) -> StatusCode {
1012    let result = catch_unwind(AssertUnwindSafe(|| {
1013        // Validate inputs
1014        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1015            return SPIR_INVALID_ARGUMENT;
1016        }
1017        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1018            return SPIR_INVALID_ARGUMENT;
1019        }
1020
1021        // Parse order
1022        let mem_order = match MemoryOrder::from_c_int(order) {
1023            Ok(o) => o,
1024            Err(_) => return SPIR_INVALID_ARGUMENT,
1025        };
1026
1027        let sampling_ref = unsafe { &*s };
1028        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1029        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1030
1031        // Convert dimensions for row-major processing
1032        // For column-major, this reverses dims and adjusts target_dim
1033        let (row_major_dims, row_major_target_dim) =
1034            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1035
1036        // Create input view directly from buffer (zero-copy)
1037        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1038
1039        // Validate that input dimension matches basis size
1040        let sampling_inner = sampling_ref.inner();
1041        let expected_basis_size = sampling_inner.basis_size();
1042        if row_major_dims[row_major_target_dim] != expected_basis_size {
1043            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1044        }
1045
1046        // Build output dimensions
1047        let n_points = sampling_inner.n_points();
1048        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, n_points);
1049
1050        // Create output view directly from buffer (zero-copy)
1051        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1052
1053        // Get backend handle (NULL means use default)
1054        let backend_handle = unsafe { get_backend_handle(backend) };
1055
1056        // Evaluate using InplaceFitter (zero-copy: writes directly to output buffer)
1057        if !InplaceFitter::evaluate_nd_dd_to(
1058            sampling_inner,
1059            backend_handle,
1060            &input_view,
1061            row_major_target_dim,
1062            &mut output_view,
1063        ) {
1064            return SPIR_NOT_SUPPORTED;
1065        }
1066
1067        SPIR_COMPUTATION_SUCCESS
1068    }));
1069
1070    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1071}
1072
1073/// Evaluate basis coefficients at sampling points (double → complex)
1074///
1075/// For Matsubara sampling: transforms real IR coefficients to complex values.
1076/// Zero-copy implementation.
1077#[unsafe(no_mangle)]
1078pub extern "C" fn spir_sampling_eval_dz(
1079    s: *const spir_sampling,
1080    backend: *const spir_gemm_backend,
1081    order: libc::c_int,
1082    ndim: libc::c_int,
1083    input_dims: *const libc::c_int,
1084    target_dim: libc::c_int,
1085    input: *const f64,
1086    out: *mut Complex64,
1087) -> StatusCode {
1088    let result = catch_unwind(AssertUnwindSafe(|| {
1089        // Validate inputs
1090        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1091            return SPIR_INVALID_ARGUMENT;
1092        }
1093        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1094            return SPIR_INVALID_ARGUMENT;
1095        }
1096
1097        // Parse order
1098        let mem_order = match MemoryOrder::from_c_int(order) {
1099            Ok(o) => o,
1100            Err(_) => return SPIR_INVALID_ARGUMENT,
1101        };
1102
1103        let sampling_ref = unsafe { &*s };
1104        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1105        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1106
1107        // Convert dimensions for row-major processing
1108        let (row_major_dims, row_major_target_dim) =
1109            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1110
1111        // Create input view directly from buffer (zero-copy)
1112        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1113
1114        // Validate that input dimension matches basis size
1115        let sampling_inner = sampling_ref.inner();
1116        let expected_basis_size = sampling_inner.basis_size();
1117        if row_major_dims[row_major_target_dim] != expected_basis_size {
1118            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1119        }
1120
1121        // Build output dimensions
1122        let n_points = sampling_inner.n_points();
1123        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, n_points);
1124
1125        // Create output view directly from buffer (zero-copy)
1126        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1127
1128        // Get backend handle (NULL means use default)
1129        let backend_handle = unsafe { get_backend_handle(backend) };
1130
1131        // Evaluate using InplaceFitter (dz: real → complex)
1132        if !InplaceFitter::evaluate_nd_dz_to(
1133            sampling_inner,
1134            backend_handle,
1135            &input_view,
1136            row_major_target_dim,
1137            &mut output_view,
1138        ) {
1139            return SPIR_NOT_SUPPORTED;
1140        }
1141
1142        SPIR_COMPUTATION_SUCCESS
1143    }));
1144
1145    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1146}
1147
1148/// Evaluate basis coefficients at sampling points (complex → complex)
1149///
1150/// For Matsubara sampling: transforms complex coefficients to complex values.
1151/// Zero-copy implementation.
1152#[unsafe(no_mangle)]
1153pub extern "C" fn spir_sampling_eval_zz(
1154    s: *const spir_sampling,
1155    backend: *const spir_gemm_backend,
1156    order: libc::c_int,
1157    ndim: libc::c_int,
1158    input_dims: *const libc::c_int,
1159    target_dim: libc::c_int,
1160    input: *const Complex64,
1161    out: *mut Complex64,
1162) -> StatusCode {
1163    let result = catch_unwind(AssertUnwindSafe(|| {
1164        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1165            return SPIR_INVALID_ARGUMENT;
1166        }
1167        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1168            return SPIR_INVALID_ARGUMENT;
1169        }
1170
1171        // Parse order
1172        let mem_order = match MemoryOrder::from_c_int(order) {
1173            Ok(o) => o,
1174            Err(_) => return SPIR_INVALID_ARGUMENT,
1175        };
1176
1177        let sampling_ref = unsafe { &*s };
1178        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1179        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1180
1181        // Convert dimensions for row-major processing
1182        let (row_major_dims, row_major_target_dim) =
1183            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1184
1185        // Create input view directly from buffer (zero-copy)
1186        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1187
1188        // Validate that input dimension matches basis size
1189        let sampling_inner = sampling_ref.inner();
1190        let expected_basis_size = sampling_inner.basis_size();
1191        if row_major_dims[row_major_target_dim] != expected_basis_size {
1192            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1193        }
1194
1195        // Build output dimensions
1196        let n_points = sampling_inner.n_points();
1197        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, n_points);
1198
1199        // Create output view directly from buffer (zero-copy)
1200        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1201
1202        // Get backend handle (NULL means use default)
1203        let backend_handle = unsafe { get_backend_handle(backend) };
1204
1205        // Evaluate using InplaceFitter (zz: complex → complex)
1206        if !InplaceFitter::evaluate_nd_zz_to(
1207            sampling_inner,
1208            backend_handle,
1209            &input_view,
1210            row_major_target_dim,
1211            &mut output_view,
1212        ) {
1213            return SPIR_NOT_SUPPORTED;
1214        }
1215
1216        SPIR_COMPUTATION_SUCCESS
1217    }));
1218
1219    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1220}
1221
1222// ============================================================================
1223// Fitting Functions (sampling points → coefficients)
1224// ============================================================================
1225
1226/// Fits values at sampling points to basis coefficients (double to double version).
1227///
1228/// Transforms values at sampling points back to basis coefficients, where both
1229/// input and output are real (double precision) values. The operation can be
1230/// performed along any dimension of a multidimensional array.
1231///
1232/// # Arguments
1233///
1234/// * `s` - Pointer to the sampling object
1235/// * `backend` - Pointer to the GEMM backend (can be null to use default)
1236/// * `order` - Memory layout order (SPIR_ORDER_ROW_MAJOR or SPIR_ORDER_COLUMN_MAJOR)
1237/// * `ndim` - Number of dimensions in the input/output arrays
1238/// * `input_dims` - Array of dimension sizes
1239/// * `target_dim` - Target dimension for the transformation (0-based)
1240/// * `input` - Input array of values at sampling points
1241/// * `out` - Output array for the fitted basis coefficients
1242///
1243/// # Returns
1244///
1245/// An integer status code:
1246/// * `0` (SPIR_COMPUTATION_SUCCESS) on success
1247/// * A non-zero error code on failure
1248///
1249/// # Notes
1250///
1251/// * The output array must be pre-allocated with the correct size
1252/// * This function performs the inverse operation of `spir_sampling_eval_dd`
1253/// * The transformation is performed using a pre-computed sampling matrix
1254///   that is factorized using SVD for efficiency
1255/// * Zero-copy implementation
1256///
1257/// # See also
1258///
1259/// * [`spir_sampling_eval_dd`]
1260/// * [`spir_sampling_fit_zz`]
1261#[unsafe(no_mangle)]
1262pub extern "C" fn spir_sampling_fit_dd(
1263    s: *const spir_sampling,
1264    backend: *const spir_gemm_backend,
1265    order: libc::c_int,
1266    ndim: libc::c_int,
1267    input_dims: *const libc::c_int,
1268    target_dim: libc::c_int,
1269    input: *const f64,
1270    out: *mut f64,
1271) -> StatusCode {
1272    let result = catch_unwind(AssertUnwindSafe(|| {
1273        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1274            return SPIR_INVALID_ARGUMENT;
1275        }
1276        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1277            return SPIR_INVALID_ARGUMENT;
1278        }
1279
1280        // Parse order
1281        let mem_order = match MemoryOrder::from_c_int(order) {
1282            Ok(o) => o,
1283            Err(_) => return SPIR_INVALID_ARGUMENT,
1284        };
1285
1286        let sampling_ref = unsafe { &*s };
1287        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1288        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1289
1290        // Convert dimensions for row-major processing
1291        let (row_major_dims, row_major_target_dim) =
1292            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1293
1294        // Create input view directly from buffer (zero-copy)
1295        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1296
1297        // Validate that input dimension matches n_points
1298        let sampling_inner = sampling_ref.inner();
1299        let expected_n_points = sampling_inner.n_points();
1300        if row_major_dims[row_major_target_dim] != expected_n_points {
1301            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1302        }
1303
1304        // Build output dimensions (replace n_points with basis_size)
1305        let basis_size = sampling_inner.basis_size();
1306        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, basis_size);
1307
1308        // Create output view directly from buffer (zero-copy)
1309        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1310
1311        // Get backend handle (NULL means use default)
1312        let backend_handle = unsafe { get_backend_handle(backend) };
1313
1314        // Fit using InplaceFitter (dd: real → real)
1315        if !InplaceFitter::fit_nd_dd_to(
1316            sampling_inner,
1317            backend_handle,
1318            &input_view,
1319            row_major_target_dim,
1320            &mut output_view,
1321        ) {
1322            return SPIR_NOT_SUPPORTED;
1323        }
1324
1325        SPIR_COMPUTATION_SUCCESS
1326    }));
1327
1328    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1329}
1330
1331/// Fits values at sampling points to basis coefficients (complex to complex version).
1332///
1333/// For more details, see [`spir_sampling_fit_dd`]
1334/// Zero-copy implementation for Tau and Matsubara (full).
1335/// MatsubaraPositiveOnly requires intermediate storage for real→complex conversion.
1336#[unsafe(no_mangle)]
1337pub extern "C" fn spir_sampling_fit_zz(
1338    s: *const spir_sampling,
1339    backend: *const spir_gemm_backend,
1340    order: libc::c_int,
1341    ndim: libc::c_int,
1342    input_dims: *const libc::c_int,
1343    target_dim: libc::c_int,
1344    input: *const Complex64,
1345    out: *mut Complex64,
1346) -> StatusCode {
1347    let result = catch_unwind(AssertUnwindSafe(|| {
1348        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1349            return SPIR_INVALID_ARGUMENT;
1350        }
1351        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1352            return SPIR_INVALID_ARGUMENT;
1353        }
1354
1355        // Parse order
1356        let mem_order = match MemoryOrder::from_c_int(order) {
1357            Ok(o) => o,
1358            Err(_) => return SPIR_INVALID_ARGUMENT,
1359        };
1360
1361        let sampling_ref = unsafe { &*s };
1362        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1363        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1364
1365        // Convert dimensions for row-major processing
1366        let (row_major_dims, row_major_target_dim) =
1367            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1368
1369        // Create input view directly from buffer (zero-copy)
1370        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1371
1372        // Validate that input dimension matches n_points
1373        let sampling_inner = sampling_ref.inner();
1374        let expected_n_points = sampling_inner.n_points();
1375        if row_major_dims[row_major_target_dim] != expected_n_points {
1376            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1377        }
1378
1379        // Build output dimensions (replace n_points with basis_size)
1380        let basis_size = sampling_inner.basis_size();
1381        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, basis_size);
1382
1383        // Create output view directly from buffer (zero-copy)
1384        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1385
1386        // Get backend handle (NULL means use default)
1387        let backend_handle = unsafe { get_backend_handle(backend) };
1388
1389        // Fit using InplaceFitter (zz: complex → complex)
1390        if !InplaceFitter::fit_nd_zz_to(
1391            sampling_inner,
1392            backend_handle,
1393            &input_view,
1394            row_major_target_dim,
1395            &mut output_view,
1396        ) {
1397            return SPIR_NOT_SUPPORTED;
1398        }
1399
1400        SPIR_COMPUTATION_SUCCESS
1401    }));
1402
1403    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1404}
1405
1406/// Fit basis coefficients from Matsubara sampling points (complex input, real output)
1407///
1408/// This function fits basis coefficients from Matsubara sampling points
1409/// using complex input and real output.
1410///
1411/// # Supported Sampling Types
1412///
1413/// - **Matsubara (full)**: ✅ Supported (takes real part of fitted complex coefficients)
1414/// - **Matsubara (positive_only)**: ✅ Supported
1415/// - **Tau**: ❌ Not supported (use `spir_sampling_fit_dd` instead)
1416///
1417/// # Notes
1418///
1419/// For full-range Matsubara sampling, this function fits complex coefficients
1420/// internally and returns their real parts. This is physically correct for
1421/// Green's functions where IR coefficients are guaranteed to be real by symmetry.
1422///
1423/// Zero-copy implementation.
1424///
1425/// # Arguments
1426///
1427/// * `s` - Pointer to the sampling object (must be Matsubara)
1428/// * `backend` - Pointer to the GEMM backend (can be null to use default)
1429/// * `order` - Memory layout order (SPIR_ORDER_COLUMN_MAJOR or SPIR_ORDER_ROW_MAJOR)
1430/// * `ndim` - Number of dimensions in the input/output arrays
1431/// * `input_dims` - Array of dimension sizes
1432/// * `target_dim` - Target dimension for the transformation (0-based)
1433/// * `input` - Input array (complex)
1434/// * `out` - Output array (real)
1435///
1436/// # Returns
1437///
1438/// - `SPIR_COMPUTATION_SUCCESS` on success
1439/// - `SPIR_NOT_SUPPORTED` if the sampling type doesn't support this operation
1440/// - Other error codes on failure
1441///
1442/// # See also
1443///
1444/// * [`spir_sampling_fit_zz`]
1445/// * [`spir_sampling_fit_dd`]
1446#[unsafe(no_mangle)]
1447pub extern "C" fn spir_sampling_fit_zd(
1448    s: *const spir_sampling,
1449    backend: *const spir_gemm_backend,
1450    order: libc::c_int,
1451    ndim: libc::c_int,
1452    input_dims: *const libc::c_int,
1453    target_dim: libc::c_int,
1454    input: *const Complex64,
1455    out: *mut f64,
1456) -> StatusCode {
1457    let result = catch_unwind(AssertUnwindSafe(|| {
1458        if s.is_null() || input_dims.is_null() || input.is_null() || out.is_null() {
1459            return SPIR_INVALID_ARGUMENT;
1460        }
1461        if ndim <= 0 || target_dim < 0 || target_dim >= ndim {
1462            return SPIR_INVALID_ARGUMENT;
1463        }
1464
1465        // Parse order
1466        let mem_order = match MemoryOrder::from_c_int(order) {
1467            Ok(o) => o,
1468            Err(_) => return SPIR_INVALID_ARGUMENT,
1469        };
1470
1471        let sampling_ref = unsafe { &*s };
1472        let dims_slice = unsafe { std::slice::from_raw_parts(input_dims, ndim as usize) };
1473        let orig_dims: Vec<usize> = dims_slice.iter().map(|&d| d as usize).collect();
1474
1475        // Convert dimensions for row-major processing
1476        let (row_major_dims, row_major_target_dim) =
1477            convert_dims_for_row_major(&orig_dims, target_dim as usize, mem_order);
1478
1479        // Create input view directly from buffer (zero-copy)
1480        let input_view = unsafe { create_dview_from_ptr(input, &row_major_dims) };
1481
1482        // Validate that input dimension matches n_points
1483        let sampling_inner = sampling_ref.inner();
1484        let expected_n_points = sampling_inner.n_points();
1485        if row_major_dims[row_major_target_dim] != expected_n_points {
1486            return crate::SPIR_INPUT_DIMENSION_MISMATCH;
1487        }
1488
1489        // Build output dimensions (replace n_points with basis_size)
1490        let basis_size = sampling_inner.basis_size();
1491        let out_dims = build_output_dims(&row_major_dims, row_major_target_dim, basis_size);
1492
1493        // Create output view directly from buffer (zero-copy)
1494        let mut output_view = unsafe { create_dviewmut_from_ptr(out, &out_dims) };
1495
1496        // Get backend handle (NULL means use default)
1497        let backend_handle = unsafe { get_backend_handle(backend) };
1498
1499        // Fit using InplaceFitter (zd: complex → real)
1500        // Note: For full-range Matsubara, this takes the real part of the fitted
1501        // complex coefficients. This is physically correct for Green's functions
1502        // where IR coefficients are guaranteed to be real by symmetry.
1503        if !InplaceFitter::fit_nd_zd_to(
1504            sampling_inner,
1505            backend_handle,
1506            &input_view,
1507            row_major_target_dim,
1508            &mut output_view,
1509        ) {
1510            return SPIR_NOT_SUPPORTED;
1511        }
1512
1513        SPIR_COMPUTATION_SUCCESS
1514    }));
1515
1516    result.unwrap_or(crate::SPIR_INTERNAL_ERROR)
1517}
1518
1519#[cfg(test)]
1520mod tests {
1521    use super::*;
1522
1523    #[test]
1524    fn test_tau_sampling_creation() {
1525        // Create a basis
1526        let mut status = 0;
1527        let kernel = crate::spir_logistic_kernel_new(10.0, &mut status);
1528        assert_eq!(status, SPIR_COMPUTATION_SUCCESS);
1529
1530        let sve = crate::spir_sve_result_new(kernel, 1e-6, -1, -1, -1, &mut status);
1531        assert_eq!(status, SPIR_COMPUTATION_SUCCESS);
1532
1533        // Limit basis size to 5
1534        let basis = crate::spir_basis_new(1, 10.0, 1.0, 1e-6, kernel, sve, 5, &mut status);
1535        assert_eq!(status, SPIR_COMPUTATION_SUCCESS);
1536
1537        // Get actual basis size
1538        let mut actual_basis_size = 0;
1539        let ret = crate::spir_basis_get_size(basis, &mut actual_basis_size);
1540        assert_eq!(ret, SPIR_COMPUTATION_SUCCESS);
1541
1542        // Create tau sampling with enough points (at least basis_size)
1543        let tau_points: Vec<f64> = (0..actual_basis_size)
1544            .map(|i| (i as f64 + 1.0) * 10.0 / (actual_basis_size as f64 + 1.0))
1545            .collect();
1546
1547        let sampling = spir_tau_sampling_new(
1548            basis,
1549            tau_points.len() as i32,
1550            tau_points.as_ptr(),
1551            &mut status,
1552        );
1553        assert_eq!(status, SPIR_COMPUTATION_SUCCESS);
1554        assert!(!sampling.is_null());
1555
1556        // Get number of points
1557        let mut n_points = 0;
1558        let ret = spir_sampling_get_npoints(sampling, &mut n_points);
1559        assert_eq!(ret, SPIR_COMPUTATION_SUCCESS);
1560        assert_eq!(n_points, actual_basis_size);
1561
1562        // Get tau points back
1563        let mut retrieved_points = vec![0.0; actual_basis_size as usize];
1564        let ret = spir_sampling_get_taus(sampling, retrieved_points.as_mut_ptr());
1565        assert_eq!(ret, SPIR_COMPUTATION_SUCCESS);
1566
1567        // Check that retrieved points match
1568        for (i, (&retrieved, &original)) in
1569            retrieved_points.iter().zip(tau_points.iter()).enumerate()
1570        {
1571            assert!(
1572                (retrieved - original).abs() < 1e-10,
1573                "Point {} mismatch: {} vs {}",
1574                i,
1575                retrieved,
1576                original
1577            );
1578        }
1579
1580        // Get condition number
1581        let mut cond = 0.0;
1582        let ret = spir_sampling_get_cond_num(sampling, &mut cond);
1583        assert_eq!(ret, SPIR_COMPUTATION_SUCCESS);
1584        assert!(cond >= 1.0); // Condition number >= 1
1585
1586        // Clean up
1587        crate::spir_sampling_release(sampling);
1588        crate::spir_basis_release(basis);
1589        crate::spir_sve_result_release(sve);
1590        crate::spir_kernel_release(kernel);
1591    }
1592}