From 4a6023854a6409b26427ef642e835dee042ac557 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sat, 13 Sep 2025 11:53:57 +0900 Subject: [PATCH 01/14] Huge commit to import the new source files --- Project.toml | 33 +- README.md | 13 +- deps/Project.toml | 3 + deps/build.jl | 33 + deps/generator.toml | 8 + deps/prologue.jl | 3 + src/C_API.jl | 1691 ++++++++++++++++++++ src/SparseIR.jl | 57 +- src/_linalg.jl | 316 ---- src/_roots.jl | 115 -- src/_specfuncs.jl | 73 - src/abstract.jl | 153 +- src/augment.jl | 86 +- src/basis.jl | 371 ++--- src/basis_set.jl | 12 +- src/dlr.jl | 260 ++- src/freq.jl | 28 +- src/gauss.jl | 112 -- src/kernel.jl | 467 +----- src/poly.jl | 605 ++----- src/sampling.jl | 646 ++++---- src/svd.jl | 29 - src/sve.jl | 332 +--- test/C_API/cinterface_core_tests.jl | 324 ++++ test/C_API/cinterface_dlr_tests.jl | 123 ++ test/C_API/cinterface_integration_tests.jl | 602 +++++++ test/C_API/cinterface_sampling_tests.jl | 735 +++++++++ test/Project.toml | 11 - test/_conftest.jl | 10 - test/_linalg.jl | 168 -- test/_multifloat_funcs.jl | 34 - test/_roots.jl | 111 -- test/_specfuncs.jl | 21 - test/aqua.jl | 5 - test/aqua_tests.jl | 7 + test/augment.jl | 142 -- test/basis.jl | 83 - test/dlr.jl | 95 -- test/freq.jl | 83 - test/gauss.jl | 39 - test/jet.jl | 47 - test/kernel.jl | 60 - test/poly.jl | 401 ----- test/runtests.jl | 10 +- test/sampling.jl | 207 --- test/spir/augment_tests.jl | 120 ++ test/spir/basis_set_tests.jl | 56 + test/spir/basis_tests.jl | 27 + test/spir/dlr_tests.jl | 367 +++++ test/spir/freq_tests.jl | 86 + test/spir/integration_tests.jl | 292 ++++ test/spir/kernel_tests.jl | 15 + test/spir/poly_tests.jl | 44 + test/spir/sampling_tests.jl | 157 ++ test/spir/sve_tests.jl | 15 + test/svd.jl | 22 - test/sve.jl | 110 -- 57 files changed, 5820 insertions(+), 4255 deletions(-) create mode 100644 deps/Project.toml create mode 100644 deps/build.jl create mode 100644 deps/generator.toml create mode 100644 deps/prologue.jl create mode 100644 src/C_API.jl delete mode 100644 src/_linalg.jl delete mode 100644 src/_roots.jl delete mode 100644 src/_specfuncs.jl delete mode 100644 src/gauss.jl delete mode 100644 src/svd.jl create mode 100644 test/C_API/cinterface_core_tests.jl create mode 100644 test/C_API/cinterface_dlr_tests.jl create mode 100644 test/C_API/cinterface_integration_tests.jl create mode 100644 test/C_API/cinterface_sampling_tests.jl delete mode 100644 test/Project.toml delete mode 100644 test/_conftest.jl delete mode 100644 test/_linalg.jl delete mode 100644 test/_multifloat_funcs.jl delete mode 100644 test/_roots.jl delete mode 100644 test/_specfuncs.jl delete mode 100644 test/aqua.jl create mode 100644 test/aqua_tests.jl delete mode 100644 test/augment.jl delete mode 100644 test/basis.jl delete mode 100644 test/dlr.jl delete mode 100644 test/freq.jl delete mode 100644 test/gauss.jl delete mode 100644 test/jet.jl delete mode 100644 test/kernel.jl delete mode 100644 test/poly.jl delete mode 100644 test/sampling.jl create mode 100644 test/spir/augment_tests.jl create mode 100644 test/spir/basis_set_tests.jl create mode 100644 test/spir/basis_tests.jl create mode 100644 test/spir/dlr_tests.jl create mode 100644 test/spir/freq_tests.jl create mode 100644 test/spir/integration_tests.jl create mode 100644 test/spir/kernel_tests.jl create mode 100644 test/spir/poly_tests.jl create mode 100644 test/spir/sampling_tests.jl create mode 100644 test/spir/sve_tests.jl delete mode 100644 test/svd.jl delete mode 100644 test/sve.jl diff --git a/Project.toml b/Project.toml index f617f25..aba6bda 100644 --- a/Project.toml +++ b/Project.toml @@ -1,21 +1,28 @@ name = "SparseIR" uuid = "4fe2279e-80f0-4adb-8463-ee114ff56b7d" -authors = ["Samuel Badr ", "Hiroshi Shinaoka ", "Markus Wallerberger "] -version = "1.1.4" +authors = ["SatoshiTerasaki ", "Samuel Badr ", "Hiroshi Shinaoka ", "Markus Wallerberger "] +version = "2.0.0a1" [deps] -Bessels = "0e736298-9ec6-45e8-9647-e4fc86a2fe38" -GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" +CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -MultiFloats = "bdf0d083-296b-4888-a5b6-7498122e68a5" -PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" [compat] -Bessels = "0.2" -GenericLinearAlgebra = "0.3.1" -LinearAlgebra = "1.6" -MultiFloats = "2" -PrecompileTools = "1" -QuadGK = "2" -julia = "1.6" +Aqua = "0.8.14" +CEnum = "0.5.0" +Libdl = "1" +LinearAlgebra = "1" +QuadGK = "2.11.2" +StableRNGs = "1.0.3" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "Test", "ReTestItems", "Random", "StableRNGs"] diff --git a/README.md b/README.md index ee6ce12..468c17c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ intermediate representation of correlation functions. It provides: > Refer also to the accompanying paper:
> **[sparse-ir: Optimal compression and sparse sampling of many-body propagators](https://doi.org/10.1016/j.softx.2022.101266)** +This is a Julia wrapper for the [libsparseir](https://github.com/SpM-lab/libsparseir) C library. + Installation ------------ SparseIR can be installed with the Julia package manager. Simply run the following from the command line: @@ -37,6 +39,13 @@ julia -e 'import Pkg; Pkg.develop(url="https://github.com/SpM-lab/SparseIR.jl")' > **Warning** > This is recommended only for developers - you won't get automatic updates! +You can also control debug output at runtime using the `SPARSEIR_DEBUG` environment variable: + +```bash +export SPARSEIR_DEBUG=1 +julia +``` + Documentation and tutorial -------------------------- Check out our [comprehensive tutorial], where self-contained @@ -46,8 +55,8 @@ Lichtenstein formula, FLEX, ... - are presented. Refer to the [API documentation] for more details on how to work with the Julia library. -There is also a [Python library] and (currently somewhat restricted) -[Fortran library] available for the IR basis and sparse sampling. +This library is built upon the [libsparseir C library](https://github.com/SpM-lab/libsparseir) with Fortran bindings. +There is also a [Python library]. [comprehensive tutorial]: https://spm-lab.github.io/sparse-ir-tutorial [API documentation]: https://spm-lab.github.io/SparseIR.jl/stable/ diff --git a/deps/Project.toml b/deps/Project.toml new file mode 100644 index 0000000..b72c81c --- /dev/null +++ b/deps/Project.toml @@ -0,0 +1,3 @@ +[deps] +Clang = "40e3b903-d033-50b4-a0cc-940c62c95e31" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/deps/build.jl b/deps/build.jl new file mode 100644 index 0000000..ea347d7 --- /dev/null +++ b/deps/build.jl @@ -0,0 +1,33 @@ +using Pkg +Pkg.activate(@__DIR__) +Pkg.instantiate() + +using Clang.Generators +using Clang.LibClang.Clang_jll + +include_dir = normpath(joinpath(@__DIR__, "../../libsparseir/include")) +sparseir_dir = joinpath(include_dir, "sparseir") + +# wrapper generator options +options = load_options(joinpath(@__DIR__, "generator.toml")) + +# add compiler flags, e.g. "-DXXXXXXXXX" +args = get_default_args() +push!(args, "-I$include_dir") + +headers = [joinpath(sparseir_dir, header) + for header in readdir(sparseir_dir) if endswith(header, ".h")] +# there is also an experimental `detect_headers` function for auto-detecting top-level headers in the directory +# headers = detect_headers(sparseir_dir, args) + +# create context +ctx = create_context(headers, args, options) + +# run generator +build!(ctx) + +# Replace line 28 with: +file_path = joinpath(@__DIR__, "../src/C_API.jl") +content = read(file_path, String) +content = replace(content, "const c_complex = ComplexF32" => "const c_complex = ComplexF64") +write(file_path, content) diff --git a/deps/generator.toml b/deps/generator.toml new file mode 100644 index 0000000..e209955 --- /dev/null +++ b/deps/generator.toml @@ -0,0 +1,8 @@ +[general] +prologue_file_path = "./prologue.jl" +library_name = "libsparseir" +output_file_path = "./../src/C_API.jl" +module_name = "C_API" +# jll_pkg_name = "LibSparseIR_jll" +export_symbol_prefixes = ["spir_", "SPIR_"] +extract_c_comment_style = "doxygen" diff --git a/deps/prologue.jl b/deps/prologue.jl new file mode 100644 index 0000000..5cfe134 --- /dev/null +++ b/deps/prologue.jl @@ -0,0 +1,3 @@ +using Libdl: dlext + +libsparseir = expanduser("~/opt/libsparseir/lib/libsparseir.$(dlext)") diff --git a/src/C_API.jl b/src/C_API.jl new file mode 100644 index 0000000..b2ad906 --- /dev/null +++ b/src/C_API.jl @@ -0,0 +1,1691 @@ +module C_API + +using CEnum: CEnum, @cenum + +using Libdl: dlext + +libsparseir = expanduser("~/opt/libsparseir/lib/libsparseir.$(dlext)") + +const c_complex = ComplexF64 + +mutable struct _spir_kernel end + +const spir_kernel = _spir_kernel + +function spir_kernel_release(obj) + ccall((:spir_kernel_release, libsparseir), Cvoid, (Ptr{spir_kernel},), obj) +end + +function spir_kernel_clone(src) + ccall((:spir_kernel_clone, libsparseir), Ptr{spir_kernel}, (Ptr{spir_kernel},), src) +end + +function spir_kernel_is_assigned(obj) + ccall((:spir_kernel_is_assigned, libsparseir), Cint, (Ptr{spir_kernel},), obj) +end + +function _spir_kernel_get_raw_ptr(obj) + ccall((:_spir_kernel_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_kernel},), obj) +end + +mutable struct _spir_funcs end + +const spir_funcs = _spir_funcs + +function spir_funcs_release(obj) + ccall((:spir_funcs_release, libsparseir), Cvoid, (Ptr{spir_funcs},), obj) +end + +function spir_funcs_clone(src) + ccall((:spir_funcs_clone, libsparseir), Ptr{spir_funcs}, (Ptr{spir_funcs},), src) +end + +function spir_funcs_is_assigned(obj) + ccall((:spir_funcs_is_assigned, libsparseir), Cint, (Ptr{spir_funcs},), obj) +end + +function _spir_funcs_get_raw_ptr(obj) + ccall((:_spir_funcs_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_funcs},), obj) +end + +mutable struct _spir_basis end + +const spir_basis = _spir_basis + +function spir_basis_release(obj) + ccall((:spir_basis_release, libsparseir), Cvoid, (Ptr{spir_basis},), obj) +end + +function spir_basis_clone(src) + ccall((:spir_basis_clone, libsparseir), Ptr{spir_basis}, (Ptr{spir_basis},), src) +end + +function spir_basis_is_assigned(obj) + ccall((:spir_basis_is_assigned, libsparseir), Cint, (Ptr{spir_basis},), obj) +end + +function _spir_basis_get_raw_ptr(obj) + ccall((:_spir_basis_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_basis},), obj) +end + +mutable struct _spir_sampling end + +const spir_sampling = _spir_sampling + +function spir_sampling_release(obj) + ccall((:spir_sampling_release, libsparseir), Cvoid, (Ptr{spir_sampling},), obj) +end + +function spir_sampling_clone(src) + ccall( + (:spir_sampling_clone, libsparseir), Ptr{spir_sampling}, (Ptr{spir_sampling},), src) +end + +function spir_sampling_is_assigned(obj) + ccall((:spir_sampling_is_assigned, libsparseir), Cint, (Ptr{spir_sampling},), obj) +end + +function _spir_sampling_get_raw_ptr(obj) + ccall( + (:_spir_sampling_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_sampling},), obj) +end + +mutable struct _spir_sve_result end + +const spir_sve_result = _spir_sve_result + +function spir_sve_result_release(obj) + ccall((:spir_sve_result_release, libsparseir), Cvoid, (Ptr{spir_sve_result},), obj) +end + +function spir_sve_result_clone(src) + ccall((:spir_sve_result_clone, libsparseir), + Ptr{spir_sve_result}, (Ptr{spir_sve_result},), src) +end + +function spir_sve_result_is_assigned(obj) + ccall((:spir_sve_result_is_assigned, libsparseir), Cint, (Ptr{spir_sve_result},), obj) +end + +function _spir_sve_result_get_raw_ptr(obj) + ccall((:_spir_sve_result_get_raw_ptr, libsparseir), + Ptr{Cvoid}, (Ptr{spir_sve_result},), obj) +end + +""" + spir_logistic_kernel_new(lambda, status) + +Creates a new logistic kernel for fermionic/bosonic analytical continuation. + +In dimensionless variables x = 2τ/β - 1, y = βω/Λ, the integral kernel is a function on [-1, 1] × [-1, 1]: + +K(x, y) = exp(-Λy(x + 1)/2)/(1 + exp(-Λy)) + +While LogisticKernel is primarily a fermionic analytic continuation kernel, it can also model the τ dependence of a bosonic correlation function as: + +∫ [exp(-Λy(x + 1)/2)/(1 - exp(-Λy))] ρ(y) dy = ∫ K(x, y) ρ'(y) dy + +where ρ'(y) = w(y)ρ(y) and the weight function w(y) = 1/tanh(Λy/2) + +!!! note + + The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. + +# Arguments + + - `lambda`: The cutoff parameter Λ (must be non-negative) + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created kernel object, or NULL if creation fails +""" +function spir_logistic_kernel_new(lambda, status) + ccall((:spir_logistic_kernel_new, libsparseir), + Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) +end + +""" + spir_reg_bose_kernel_new(lambda, status) + +Creates a new regularized bosonic kernel for analytical continuation. + +In dimensionless variables x = 2τ/β - 1, y = βω/Λ, the integral kernel is a function on [-1, 1] × [-1, 1]: + +K(x, y) = y * exp(-Λy(x + 1)/2)/(exp(-Λy) - 1) + +Special care is taken in evaluating this expression around y = 0 to handle the singularity. The kernel is specifically designed for bosonic functions and includes proper regularization to handle numerical stability issues. + +!!! note + + This kernel is specifically designed for bosonic correlation functions and should not be used for fermionic cases. + +!!! note + + The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. + +# Arguments + + - `lambda`: The cutoff parameter Λ (must be non-negative) + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created kernel object, or NULL if creation fails +""" +function spir_reg_bose_kernel_new(lambda, status) + ccall((:spir_reg_bose_kernel_new, libsparseir), + Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) +end + +""" + spir_kernel_domain(k, xmin, xmax, ymin, ymax) + +Retrieves the domain boundaries of a kernel function. + +This function obtains the domain boundaries (ranges) for both the x and y variables of the specified kernel function. The kernel domain is typically defined as a rectangle in the (x,y) plane. + +!!! note + + For the logistic and regularized bosonic kernels, the domain is typically [-1, 1] × [-1, 1] in dimensionless variables. + +# Arguments + + - `k`: Pointer to the kernel object whose domain is to be retrieved. + - `xmin`: Pointer to store the minimum value of the x-range. + - `xmax`: Pointer to store the maximum value of the x-range. + - `ymin`: Pointer to store the minimum value of the y-range. + - `ymax`: Pointer to store the maximum value of the y-range. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_kernel_domain(k, xmin, xmax, ymin, ymax) + ccall((:spir_kernel_domain, libsparseir), Cint, + (Ptr{spir_kernel}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), + k, xmin, xmax, ymin, ymax) +end + +""" + spir_sve_result_new(k, epsilon, cutoff, lmax, n_gauss, Twork, status) + +Perform truncated singular value expansion (SVE) of a kernel. + +Computes a truncated singular value expansion of an integral kernel K: [xmin, xmax] × [ymin, ymax] → ℝ in the form: + +K(x, y) = ∑ s[l] * u[l](x) * v[l](y) for l = 1, 2, 3, ... + +where: - s[l] are singular values in non-increasing order - u[l](x) are left singular functions, forming an orthonormal system on [xmin, xmax] - v[l](y) are right singular functions, forming an orthonormal system on [ymin, ymax] + +The SVE is computed by mapping it onto a singular value decomposition (SVD) of a matrix using piecewise Legendre polynomial expansion. The accuracy of the computation is controlled by the epsilon parameter, which determines: - The relative magnitude of included singular values - The accuracy of computed singular values and vectors + +!!! note + + The computation automatically uses optimized strategies: - For centrosymmetric kernels, specialized algorithms are employed - If Twork is [`SPIR_TWORK_AUTO`](@ref), the working precision is automatically adjusted to meet accuracy requirements based on epsilon - If epsilon is below √ε (where ε is machine epsilon), a warning is issued and higher precision arithmetic is used if possible. + +!!! note + + The returned object must be freed using spir\\_release\\_sve\\_result when no longer needed + +# Arguments + + - `k`: Pointer to the kernel object for which to compute SVE + - `epsilon`: Accuracy target for the basis. Determines: - The relative magnitude of included singular values - The accuracy of computed singular values and vectors + - `cutoff`: Cutoff value for singular values + - `lmax`: Maximum number of Legendre polynomials to use + - `n_gauss`: Number of Gauss points for numerical integration + - `Twork`: Working data type for computations (sve). Must be one of: - [`SPIR_TWORK_FLOAT64`](@ref) (0): Use double precision (64-bit) - [`SPIR_TWORK_FLOAT64X2`](@ref) (1): Use extended precision (128-bit) - [`SPIR_TWORK_AUTO`](@ref) (-1): Automatically choose precision based on epsilon + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created SVE result, or NULL if creation fails + +# See also + +spir\\_release\\_sve\\_result +""" +function spir_sve_result_new(k, epsilon, cutoff, lmax, n_gauss, Twork, status) + ccall((:spir_sve_result_new, libsparseir), Ptr{spir_sve_result}, + (Ptr{spir_kernel}, Cdouble, Cdouble, Cint, Cint, Cint, Ptr{Cint}), + k, epsilon, cutoff, lmax, n_gauss, Twork, status) +end + +""" + spir_sve_result_get_size(sve, size) + +Gets the number of singular values/vectors in an SVE result. + +This function returns the number of singular values and corresponding singular vectors contained in the specified SVE result object. This number is needed to allocate arrays of the correct size when retrieving singular values or evaluating singular vectors. + +# Arguments + + - `sve`: Pointer to the SVE result object + - `size`: Pointer to store the number of singular values/vectors + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_sve_result_get_size(sve, size) + ccall((:spir_sve_result_get_size, libsparseir), Cint, + (Ptr{spir_sve_result}, Ptr{Cint}), sve, size) +end + +""" + spir_sve_result_get_svals(sve, svals) + +Gets the singular values from an SVE result. + +This function retrieves all singular values from the specified SVE result object. The singular values are stored in descending order in the output array. + +# Arguments + + - `sve`: Pointer to the SVE result object + - `svals`: Pre-allocated array to store the singular values. Must have size at least equal to the value returned by [`spir_sve_result_get_size`](@ref)() + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sve_result_get_size`](@ref) +""" +function spir_sve_result_get_svals(sve, svals) + ccall((:spir_sve_result_get_svals, libsparseir), Cint, + (Ptr{spir_sve_result}, Ptr{Cdouble}), sve, svals) +end + +""" + spir_funcs_get_size(funcs, size) + +Gets the number of functions in a functions object. + +This function returns the number of functions contained in the specified functions object. This number is needed to allocate arrays of the correct size when evaluating the functions. + +# Arguments + + - `funcs`: Pointer to the functions object + - `size`: Pointer to store the number of functions + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_funcs_get_size(funcs, size) + ccall((:spir_funcs_get_size, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cint}), funcs, size) +end + +""" + spir_funcs_get_slice(funcs, nslice, indices, status) + +Creates a new function object containing a subset of functions from the input. + +This function creates a new function object that contains only the functions specified by the indices array. The indices must be valid (within range and no duplicates). + +!!! note + + The caller is responsible for freeing the returned object using spir\\_funcs\\_free + +!!! note + + If status is non-zero, the returned pointer will be NULL + +# Arguments + + - `funcs`: Pointer to the source function object + - `nslice`: Number of functions to select (length of indices array) + - `indices`: Array of indices specifying which functions to include in the slice + - `status`: Pointer to store the status code (0 for success, non-zero for error) + +# Returns + +Pointer to the new function object containing the selected functions, or NULL on error +""" +function spir_funcs_get_slice(funcs, nslice, indices, status) + ccall((:spir_funcs_get_slice, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_funcs}, Cint, Ptr{Cint}, Ptr{Cint}), funcs, nslice, indices, status) +end + +""" + spir_funcs_eval(funcs, x, out) + +Evaluates functions at a single point in the imaginary-time domain or the real frequency domain. + +This function evaluates all functions at a specified point x. The values of each basis function at x are stored in the output array. The output array out[j] contains the value of the j-th function evaluated at x. + +!!! note + + The output array must be pre-allocated with sufficient size to store all function values + +# Arguments + + - `funcs`: Pointer to a functions object + - `x`: Point at which to evaluate the functions + - `out`: Pre-allocated array to store the evaluation results. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_funcs_eval(funcs, x, out) + ccall((:spir_funcs_eval, libsparseir), Cint, + (Ptr{spir_funcs}, Cdouble, Ptr{Cdouble}), funcs, x, out) +end + +""" + spir_funcs_eval_matsu(funcs, x, out) + +Evaluate a funcs object at a single Matsubara frequency + +This function evaluates the basis functions at a single Matsubara frequency index. The output array will contain the values of all basis functions at the specified frequency. + +# Arguments + + - `funcs`: Pointer to the funcs object to evaluate + - `x`: The Matsubara frequency index (integer) + - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store all basis function values. The values are stored in the order of basis functions. + +# Returns + +int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure +""" +function spir_funcs_eval_matsu(funcs, x, out) + ccall((:spir_funcs_eval_matsu, libsparseir), Cint, + (Ptr{spir_funcs}, Int64, Ptr{c_complex}), funcs, x, out) +end + +""" + spir_funcs_batch_eval(funcs, order, num_points, xs, out) + +Evaluate a funcs object at multiple points in the imaginary-time domain or the real frequency domain + +This function evaluates the basis functions at multiple points. The points can be either in the imaginary-time domain or the real frequency domain, depending on the type of the funcs object (u or v basis functions). + +The output array can be stored in either row-major or column-major order, specified by the order parameter. In row-major order, the output is stored as (num\\_points, nfuncs), while in column-major order, it is stored as (nfuncs, num\\_points). + +# Arguments + + - `funcs`: Pointer to the funcs object to evaluate + - `order`: Memory layout of the output array: - [`SPIR_ORDER_ROW_MAJOR`](@ref): (num\\_points, nfuncs) - [`SPIR_ORDER_COLUMN_MAJOR`](@ref): (nfuncs, num\\_points) + - `num_points`: Number of points to evaluate + - `xs`: Array of points to evaluate at. The points should be in the appropriate domain (imaginary time for u basis, real frequency for v basis) + - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store num\\_points * nfuncs values, where nfuncs is the number of basis functions. + +# Returns + +int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure +""" +function spir_funcs_batch_eval(funcs, order, num_points, xs, out) + ccall((:spir_funcs_batch_eval, libsparseir), Cint, + (Ptr{spir_funcs}, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + funcs, order, num_points, xs, out) +end + +""" + spir_funcs_batch_eval_matsu(funcs, order, num_freqs, matsubara_freq_indices, out) + +Evaluates basis functions at multiple Matsubara frequencies. + +This function evaluates all functions contained in a functions object at the specified Matsubara frequency indices. The values of each function at each frequency are stored in the output array. + +!!! note + + The output array must be pre-allocated with sufficient size to store all function values at all requested frequencies. Indices n correspond to ωn = nπ/β, where n are odd for fermionic frequencies and even for bosonic frequencies. + +# Arguments + + - `funcs`: Pointer to the functions object + - `order`: Specifies the memory layout of the output array: [`SPIR_ORDER_ROW_MAJOR`](@ref) for row-major order (frequency index varies fastest), [`SPIR_ORDER_COLUMN_MAJOR`](@ref) for column-major order (function index varies fastest) + - `num_freqs`: Number of Matsubara frequencies at which to evaluate + - `matsubara_freq_indices`: Array of Matsubara frequency indices + - `out`: Pre-allocated array to store the evaluation results. The results are stored as a 2D array of size num\\_freqs x n\\_funcs. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_funcs_batch_eval_matsu(funcs, order, num_freqs, matsubara_freq_indices, out) + ccall((:spir_funcs_batch_eval_matsu, libsparseir), Cint, + (Ptr{spir_funcs}, Cint, Cint, Ptr{Int64}, Ptr{c_complex}), + funcs, order, num_freqs, matsubara_freq_indices, out) +end + +""" + spir_funcs_get_n_roots(funcs, n_roots) + +Gets the number of roots of a funcs object. + +This function returns the number of roots of the specified funcs object. This function is only available for continuous functions. + +# Arguments + + - `funcs`: Pointer to the funcs object + - `n_roots`: Pointer to store the number of roots + +# Returns + +An integer status code: +""" +function spir_funcs_get_n_roots(funcs, n_roots) + ccall((:spir_funcs_get_n_roots, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_roots) +end + +""" + spir_funcs_get_roots(funcs, roots) + +Gets the roots of a funcs object. + +This function returns the roots of the specified funcs object in the non-ascending order. If the size of the funcs object is greater than 1, the roots for all the functions are returned. This function is only available for continuous functions. + +# Arguments + + - `funcs`: Pointer to the funcs object + - `n_roots`: Pointer to store the number of roots + - `roots`: Pointer to store the roots + +# Returns + +An integer status code: +""" +function spir_funcs_get_roots(funcs, roots) + ccall((:spir_funcs_get_roots, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, roots) +end + +""" + spir_basis_new(statistics, beta, omega_max, k, sve, max_size, status) + +Creates a new finite temperature IR basis using a pre-computed SVE result. + +This function creates a intermediate representation (IR) basis using a pre-computed singular value expansion (SVE) result. This allows for reusing an existing SVE computation, which can be more efficient than recomputing it. + +!!! note + + Using a pre-computed SVE can significantly improve performance when creating multiple basis objects with the same kernel + +# Arguments + + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `beta`: Inverse temperature β (must be positive) + - `omega_max`: Frequency cutoff ωmax (must be non-negative) + - `k`: Pointer to the kernel object used for the basis construction + - `sve`: Pointer to a pre-computed SVE result for the kernel + - `max_size`: Maximum number of basis functions to include. If -1, all + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created basis object, or NULL if creation fails + +# See also + +[`spir_sve_result_new`](@ref), spir\\_release\\_finite\\_temp\\_basis +""" +function spir_basis_new(statistics, beta, omega_max, k, sve, max_size, status) + ccall((:spir_basis_new, libsparseir), Ptr{spir_basis}, + (Cint, Cdouble, Cdouble, Ptr{spir_kernel}, Ptr{spir_sve_result}, Cint, Ptr{Cint}), + statistics, beta, omega_max, k, sve, max_size, status) +end + +""" + spir_basis_get_size(b, size) + +Gets the size (number of basis functions) of a finite temperature basis. + +This function returns the number of basis functions in the specified finite temperature basis object. This size determines the dimensionality of the basis and is needed when allocating arrays for basis function evaluations. + +!!! note + + For an IR basis, the size is determined automatically during basis construction based on the specified parameters (β, ωmax, ε) and the kernel's singular value expansion. + +!!! note + + For a DLR basis, the size is the number of poles. + +# Arguments + + - `b`: Pointer to the finite temperature basis object + - `size`: Pointer to store the number of basis functions + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_basis_get_size(b, size) + ccall((:spir_basis_get_size, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, size) +end + +""" + spir_basis_get_svals(b, svals) + +Gets the singular values of a finite temperature basis. + +This function returns the singular values of the specified finite temperature basis object. The singular values are the square roots of the eigenvalues of the covariance matrix of the basis functions. + +!!! note + + The singular values are ordered in descending order + +!!! note + + The number of singular values is equal to the basis size + +# Arguments + + - `sve`: Pointer to the finite temperature basis object + - `svals`: Pointer to store the singular values + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_size`](@ref) +""" +function spir_basis_get_svals(b, svals) + ccall((:spir_basis_get_svals, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) +end + +""" + spir_basis_get_stats(b, statistics) + +Gets the statistics type (Fermionic or Bosonic) of a finite temperature basis. + +This function returns the statistics type of the specified finite temperature basis object. The statistics type determines whether the basis is for fermionic or bosonic Green's functions. + +!!! note + + The statistics type is determined during basis construction and cannot be changed + +!!! note + + The statistics type affects the form of the basis functions and the sampling points used for evaluation. + +# Arguments + + - `b`: Pointer to the finite temperature basis object + - `statistics`: Pointer to store the statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_basis_get_stats(b, statistics) + ccall((:spir_basis_get_stats, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), b, statistics) +end + +""" + spir_basis_get_singular_values(b, svals) + +Gets the singular values of a finite temperature basis. + +This function returns the singular values of the specified finite temperature basis object. The singular values are the square roots of the eigenvalues of the covariance matrix of the basis functions. +""" +function spir_basis_get_singular_values(b, svals) + ccall((:spir_basis_get_singular_values, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) +end + +""" + spir_basis_get_u(b, status) + +Gets the basis functions of a finite temperature basis. + +This function returns an object representing the basis functions in the imaginary-time domain of the specified finite temperature basis. + +!!! note + + The returned object must be freed using spir\\_release\\_funcs when no longer needed + +# Arguments + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + +# Returns + +Pointer to the basis functions object, or NULL if creation fails + +# See also + +spir\\_release\\_funcs +""" +function spir_basis_get_u(b, status) + ccall((:spir_basis_get_u, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) +end + +""" + spir_basis_get_v(b, status) + +Gets the basis functions of a finite temperature basis. + +This function returns an object representing the basis functions in the real-frequency domain of the specified finite temperature basis. + +!!! note + + The returned object must be freed using spir\\_release\\_funcs when no longer needed + +# Arguments + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + +# Returns + +Pointer to the basis functions object, or NULL if creation fails + +# See also + +spir\\_release\\_funcs +""" +function spir_basis_get_v(b, status) + ccall((:spir_basis_get_v, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) +end + +""" + spir_basis_get_uhat(b, status) + +Gets the basis functions in Matsubara frequency domain. + +This function returns an object representing the basis functions in the Matsubara-frequency domain of the specified finite temperature basis. + +!!! note + + The returned object must be freed using spir\\_release\\_funcs when no longer needed + +# Arguments + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + +# Returns + +Pointer to the basis functions object, or NULL if creation fails + +# See also + +spir\\_release\\_funcs +""" +function spir_basis_get_uhat(b, status) + ccall((:spir_basis_get_uhat, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) +end + +""" + spir_basis_get_n_default_taus(b, num_points) + +Gets the number of default tau sampling points for an IR basis. + +This function returns the number of default sampling points in imaginary time (τ) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in imaginary time. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `num_points`: Pointer to store the number of sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_default_taus`](@ref) +""" +function spir_basis_get_n_default_taus(b, num_points) + ccall((:spir_basis_get_n_default_taus, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) +end + +""" + spir_basis_get_default_taus(b, points) + +Gets the default tau sampling points for an IR basis. + +This function fills the provided array with the default sampling points in imaginary time (τ) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in imaginary time. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The array must be pre-allocated with size >= [`spir_basis_get_n_default_taus`](@ref)(b) + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `points`: Pre-allocated array to store the τ sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_n_default_taus`](@ref) +""" +function spir_basis_get_default_taus(b, points) + ccall((:spir_basis_get_default_taus, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) +end + +""" + spir_basis_get_n_default_ws(b, num_points) + +Gets the number of default omega sampling points for an IR basis. + +This function returns the number of default sampling points in real frequency (ω) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in real frequency. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `num_points`: Pointer to store the number of sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_default_ws`](@ref) +""" +function spir_basis_get_n_default_ws(b, num_points) + ccall((:spir_basis_get_n_default_ws, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), b, num_points) +end + +""" + spir_basis_get_default_ws(b, points) + +Gets the default omega sampling points for an IR basis. + +This function fills the provided array with the default sampling points in real frequency (ω) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in real frequency. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The array must be pre-allocated with size >= [`spir_basis_get_n_default_ws`](@ref)(b) + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `points`: Pre-allocated array to store the ω sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_n_default_ws`](@ref) +""" +function spir_basis_get_default_ws(b, points) + ccall((:spir_basis_get_default_ws, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), b, points) +end + +""" + spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) + + - +Gets the default tau sampling points for ann IR basis. + +This function returns default tau sampling points for an IR basis object. + +# Arguments + + - `b`: Pointer to the basis object + - `n_points`: Number of requested sampling points. + - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. + - `n_points_returned`: Number of sampling points returned. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success +""" +function spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_taus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), + b, n_points, points, n_points_returned) +end + +""" + spir_basis_get_n_default_matsus(b, positive_only, num_points) + +Gets the number of default Matsubara sampling points for an IR basis. + +This function returns the number of default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `num_points`: Pointer to store the number of sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_default_matsus`](@ref) +""" +function spir_basis_get_n_default_matsus(b, positive_only, num_points) + ccall((:spir_basis_get_n_default_matsus, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Ptr{Cint}), b, positive_only, num_points) +end + +""" + spir_basis_get_default_matsus(b, positive_only, points) + +Gets the default Matsubara sampling points for an IR basis. + +This function fills the provided array with the default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The array must be pre-allocated with size >= [`spir_basis_get_n_default_matsus`](@ref)(b) + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +!!! note + + For fermionic case, the indices n give frequencies ωn = (2n + 1)π/β + +!!! note + + For bosonic case, the indices n give frequencies ωn = 2nπ/β + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `points`: Pre-allocated array to store the Matsubara frequency indices + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_n_default_matsus`](@ref) +""" +function spir_basis_get_default_matsus(b, positive_only, points) + ccall((:spir_basis_get_default_matsus, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Ptr{Int64}), b, positive_only, points) +end + +""" + spir_basis_get_n_default_matsus_ext(b, positive_only, L, num_points_returned) + +Gets the number of default Matsubara sampling points for an IR basis. + +This function returns the number of default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. + +!!! note + + This function is only available for IR basis objects + +!!! note + + The default sampling points are chosen to provide near-optimal conditioning for the given basis size + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `L`: Number of requested sampling points. + - `num_points_returned`: Pointer to store the number of sampling points returned. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_basis_get_default_matsus`](@ref) +""" +function spir_basis_get_n_default_matsus_ext(b, positive_only, L, num_points_returned) + ccall((:spir_basis_get_n_default_matsus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Cint, Ptr{Cint}), b, positive_only, L, num_points_returned) +end + +""" + spir_basis_get_default_matsus_ext(b, positive_only, n_points, points, n_points_returned) + +Gets the default Matsubara sampling points for an IR basis. + +This function fills the provided array with the default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. + +# Arguments + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `n_points`: Number of requested sampling points. + - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. + - `n_points_returned`: Number of sampling points returned. + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success +""" +function spir_basis_get_default_matsus_ext( + b, positive_only, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_matsus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), + b, positive_only, n_points, points, n_points_returned) +end + +""" + spir_dlr_new(b, status) + +Creates a new Discrete Lehmann Representation (DLR) basis. + +This function implements a variant of the discrete Lehmann representation (DLR). Unlike the IR which uses truncated singular value expansion of the analytic continuation kernel K, the DLR is based on a "sketching" of K. The resulting basis is a linear combination of discrete set of poles on the real-frequency axis, continued to the imaginary-frequency axis: + +G(iν) = ∑ a[i] * reg[i] / (iν - w[i]) for i = 1, 2, ..., L + +where: - a[i] are the expansion coefficients - w[i] are the poles on the real axis - reg[i] are the regularization factors, which are 1 for fermionic frequencies. For bosonic frequencies, we take reg[i] = tanh(βω[i]/2) (logistic kernel), reg[i] = w[i] (regularized bosonic kernel). The DLR basis functions are given by u[i](i%CE%BD) = reg[i] / (iν - w[i]) in the imaginary-frequency domain. In the imaginary-time domain, the basis functions are given by u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 + exp(-w[i]β)) for fermionic frequencies, u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 - exp(-w[i]β)) for bosonic frequencies. - iν are Matsubara frequencies + +# Arguments + + - `b`: Pointer to a finite temperature basis object + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created DLR object, or NULL if creation fails +""" +function spir_dlr_new(b, status) + ccall((:spir_dlr_new, libsparseir), Ptr{spir_basis}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) +end + +""" + spir_dlr_new_with_poles(b, npoles, poles, status) + +Creates a new Discrete Lehmann Representation (DLR) with custom poles. + +This function creates a DLR basis with user-specified pole locations on the real-frequency axis. This allows for more control over the pole selection compared to the automatic pole selection in [`spir_dlr_new`](@ref). + +# Arguments + + - `b`: Pointer to a finite temperature basis object + - `npoles`: Number of poles to use in the representation + - `poles`: Array of pole locations on the real-frequency axis + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created DLR object, or NULL if creation fails +""" +function spir_dlr_new_with_poles(b, npoles, poles, status) + ccall((:spir_dlr_new_with_poles, libsparseir), Ptr{spir_basis}, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, npoles, poles, status) +end + +""" + spir_dlr_get_npoles(dlr, num_poles) + +Gets the number of poles in a DLR. + +This function returns the number of poles in the specified DLR object. + +# Arguments + + - `dlr`: Pointer to the DLR object + - `num_poles`: Pointer to store the number of poles + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_dlr_get_poles`](@ref) +""" +function spir_dlr_get_npoles(dlr, num_poles) + ccall((:spir_dlr_get_npoles, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), dlr, num_poles) +end + +""" + spir_dlr_get_poles(dlr, poles) + +Gets the poles in a DLR. + +This function returns the poles in the specified DLR object. + +# Arguments + + - `dlr`: Pointer to the DLR object + - `poles`: Pointer to store the poles + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_dlr_get_npoles`](@ref) +""" +function spir_dlr_get_poles(dlr, poles) + ccall((:spir_dlr_get_poles, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), dlr, poles) +end + +""" + spir_ir2dlr_dd(dlr, order, ndim, input_dims, target_dim, input, out) + +Transforms a given input array from the Intermediate Representation (IR) to the Discrete Lehmann Representation (DLR) using the specified DLR object. This version handles real (double precision) input and output arrays. + +!!! note + + The input and output arrays must be allocated with sufficient memory. The size of the input and output arrays should match the dimensions specified. The order type determines the memory layout of the input and output arrays. The function assumes that the input array is in the specified order type. The output array will be in the specified order type. + +# Arguments + + - `dlr`: Pointer to the DLR basis object + - `order`: Order type (C or Fortran) + - `ndim`: Number of dimensions of input/output arrays + - `input_dims`: Array of dimensions + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input coefficients array in IR + - `out`: Output array in DLR + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +spir\\_ir2dlr, [`spir_dlr2ir_dd`](@ref) +""" +function spir_ir2dlr_dd(dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_dd, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + dlr, order, ndim, input_dims, target_dim, input, out) +end + +function spir_ir2dlr_zz(dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_zz, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + dlr, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_dlr2ir_dd(dlr, order, ndim, input_dims, target_dim, input, out) + +Transforms coefficients from DLR basis to IR representation. + +This function converts expansion coefficients from the Discrete Lehmann Representation (DLR) basis to the Intermediate Representation (IR) basis. The transformation is performed using the fitting matrix: + +g\\_IR = fitmat * g\\_DLR + +where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficients in the DLR basis - fitmat is the transformation matrix + +!!! note + + The output array must be pre-allocated with the correct size + +!!! note + + The input and output arrays must be contiguous in memory + +!!! note + + The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation + +# Arguments + + - `dlr`: Pointer to the DLR object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of DLR coefficients (double precision) + - `out`: Output array for the IR coefficients (double precision) + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +spir\\_ir2dlr +""" +function spir_dlr2ir_dd(dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_dd, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + dlr, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_dlr2ir_zz(dlr, order, ndim, input_dims, target_dim, input, out) + +Transforms coefficients from DLR basis to IR representation. This version handles complex input and output arrays. + +This function converts expansion coefficients from the Discrete Lehmann Representation (DLR) basis to the Intermediate Representation (IR) basis. The transformation is performed using the fitting matrix: + +g\\_IR = fitmat * g\\_DLR + +where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficients in the DLR basis - fitmat is the transformation matrix + +!!! note + + The output array must be pre-allocated with the correct size + +!!! note + + The input and output arrays must be contiguous in memory + +!!! note + + The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation + +# Arguments + + - `dlr`: Pointer to the DLR object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of DLR coefficients (complex) + - `out`: Output array for the IR coefficients (complex) + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_ir2dlr_zz`](@ref), [`spir_dlr2ir_dd`](@ref) +""" +function spir_dlr2ir_zz(dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_zz, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + dlr, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_tau_sampling_new(b, num_points, points, status) + +Creates a new tau sampling object for sparse sampling in imaginary time with custom sampling points. + +Constructs a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in imaginary time (τ). The sampling points are provided by the user, allowing for custom sampling strategies. + +!!! note + + The sampling points should be chosen to ensure numerical stability and accuracy for the given basis + +!!! note + + The sampling matrix is automatically factorized using SVD for efficient transformations + +!!! note + + The returned object must be freed using spir\\_release\\_sampling when no longer needed + +# Arguments + + - `b`: Pointer to a finite temperature basis object + - `num_points`: Number of sampling points + - `points`: Array of sampling points in imaginary time (τ) + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created sampling object, or NULL if creation fails + +# See also + +spir\\_release\\_sampling +""" +function spir_tau_sampling_new(b, num_points, points, status) + ccall((:spir_tau_sampling_new, libsparseir), Ptr{spir_sampling}, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, num_points, points, status) +end + +""" + spir_tau_sampling_new_with_matrix(order, statistics, basis_size, num_points, points, matrix, status) + +Creates a new tau sampling object for sparse sampling in imaginary time with custom sampling points and a pre-computed matrix. + +This function creates a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in imaginary time (τ). The sampling points are provided by the user, allowing for custom sampling strategies. + +# Arguments + + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `basis_size`: Basis size + - `num_points`: Number of sampling points + - `points`: Array of sampling points in imaginary time (τ) + - `matrix`: Pre-computed matrix for the sampling points (num\\_points x basis\\_size). For Matsubara sampling, this should be a complex matrix. + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created sampling object, or NULL if creation fails +""" +function spir_tau_sampling_new_with_matrix( + order, statistics, basis_size, num_points, points, matrix, status) + ccall((:spir_tau_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, + (Cint, Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}), + order, statistics, basis_size, num_points, points, matrix, status) +end + +""" + spir_matsu_sampling_new(b, positive_only, num_points, points, status) + +Creates a new Matsubara sampling object for sparse sampling in Matsubara frequencies with custom sampling points. + +Constructs a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in Matsubara frequencies (iωn). The sampling points are provided by the user, allowing for custom sampling strategies. + +# Arguments + + - `b`: Pointer to a finite temperature basis object + - `positive_only`: If true, only positive frequencies are used + - `num_points`: Number of sampling points + - `points`: Array of Matsubara frequency indices (n) for the sampling points + - `status`: Pointer to store the status code + +# Returns + +Pointer to the newly created sampling object, or NULL if creation fails +""" +function spir_matsu_sampling_new(b, positive_only, num_points, points, status) + ccall((:spir_matsu_sampling_new, libsparseir), Ptr{spir_sampling}, + (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), + b, positive_only, num_points, points, status) +end + +""" + spir_matsu_sampling_new_with_matrix(order, statistics, basis_size, positive_only, num_points, points, matrix, status) + +Creates a new Matsubara sampling object for sparse sampling in Matsubara frequencies with custom sampling points and a pre-computed evaluation matrix. + +This function creates a sampling object that can be used to evaluate and fit functions at specific Matsubara frequencies. The sampling points and evaluation matrix are provided directly, allowing for custom sampling configurations. + +# Arguments + + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `basis_size`: Basis size + - `positive_only`: If true, only positive Matsubara frequencies are used + - `num_points`: Number of sampling points + - `points`: Array of Matsubara frequencies (integer indices) + - `matrix`: Pre-computed evaluation matrix of size (num\\_points × basis\\_size) + - `status`: Pointer to store the status code + +# Returns + +Pointer to the new sampling object, or NULL if creation fails + +# See also + +[`spir_matsu_sampling_new`](@ref) +""" +function spir_matsu_sampling_new_with_matrix( + order, statistics, basis_size, positive_only, num_points, points, matrix, status) + ccall((:spir_matsu_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, + (Cint, Cint, Cint, Bool, Cint, Ptr{Int64}, Ptr{c_complex}, Ptr{Cint}), order, + statistics, basis_size, positive_only, num_points, points, matrix, status) +end + +""" + spir_sampling_get_npoints(s, num_points) + +Gets the number of sampling points in a sampling object. + +This function returns the number of sampling points used in the specified sampling object. This number is needed to allocate arrays of the correct size when retrieving the actual sampling points. + +# Arguments + + - `s`: Pointer to the sampling object + - `num_points`: Pointer to store the number of sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sampling_get_taus`](@ref), [`spir_sampling_get_matsus`](@ref) +""" +function spir_sampling_get_npoints(s, num_points) + ccall((:spir_sampling_get_npoints, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cint}), s, num_points) +end + +""" + spir_sampling_get_taus(s, points) + +Gets the imaginary time sampling points. + +This function fills the provided array with the imaginary time (τ) sampling points used in the specified sampling object. The array must be pre-allocated with sufficient size (use [`spir_sampling_get_npoints`](@ref) to determine the required size). + +!!! note + + The array must be pre-allocated with size >= [`spir_sampling_get_npoints`](@ref)(s) + +# Arguments + + - `s`: Pointer to the sampling object + - `points`: Pre-allocated array to store the τ sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sampling_get_npoints`](@ref) +""" +function spir_sampling_get_taus(s, points) + ccall((:spir_sampling_get_taus, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cdouble}), s, points) +end + +""" + spir_sampling_get_matsus(s, points) + +Gets the Matsubara frequency sampling points. + +This function fills the provided array with the Matsubara frequency indices (n) used in the specified sampling object. The actual Matsubara frequencies are ωn = (2n + 1)π/β for fermionic case and ωn = 2nπ/β for bosonic case. The array must be pre-allocated with sufficient size (use [`spir_sampling_get_npoints`](@ref) to determine the required size). + +!!! note + + The array must be pre-allocated with size >= [`spir_sampling_get_npoints`](@ref)(s) + +!!! note + + For fermionic case, the indices n give frequencies ωn = (2n + 1)π/β + +!!! note + + For bosonic case, the indices n give frequencies ωn = 2nπ/β + +# Arguments + + - `s`: Pointer to the sampling object + - `points`: Pre-allocated array to store the Matsubara frequency indices + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sampling_get_npoints`](@ref) +""" +function spir_sampling_get_matsus(s, points) + ccall((:spir_sampling_get_matsus, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Int64}), s, points) +end + +""" + spir_sampling_get_cond_num(s, cond_num) + +Gets the condition number of the sampling matrix. + +This function returns the condition number of the sampling matrix used in the specified sampling object. The condition number is a measure of how well- conditioned the sampling matrix is. + +!!! note + + A large condition number indicates that the sampling matrix is ill-conditioned, which may lead to numerical instability in transformations + +!!! note + + The condition number is the ratio of the largest to smallest singular value of the sampling matrix + +# Arguments + + - `s`: Pointer to the sampling object + - `cond_num`: Pointer to store the condition number + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure +""" +function spir_sampling_get_cond_num(s, cond_num) + ccall((:spir_sampling_get_cond_num, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cdouble}), s, cond_num) +end + +""" + spir_sampling_eval_dd(s, order, ndim, input_dims, target_dim, input, out) + +Evaluates basis coefficients at sampling points (double to double version). + +Transforms basis coefficients to values at sampling points, where both input and output are real (double precision) values. The operation can be performed along any dimension of a multidimensional array. + +!!! note + + For optimal performance, the target dimension should be either the first (0) or the last (ndim-1) dimension to avoid large temporary array allocations + +!!! note + + The output array must be pre-allocated with the correct size + +!!! note + + The input and output arrays must be contiguous in memory + +!!! note + + The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency + +# Arguments + + - `s`: Pointer to the sampling object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of basis coefficients + - `out`: Output array for the evaluated values at sampling points + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sampling_eval_dz`](@ref), [`spir_sampling_eval_zz`](@ref) +""" +function spir_sampling_eval_dd(s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dd, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + s, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_sampling_eval_dz(s, order, ndim, input_dims, target_dim, input, out) + +Evaluates basis coefficients at sampling points (double to complex version). + +For more details, see [`spir_sampling_eval_dd`](@ref) + +# See also + +[`spir_sampling_eval_dd`](@ref) +""" +function spir_sampling_eval_dz(s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_sampling_eval_zz(s, order, ndim, input_dims, target_dim, input, out) + +Evaluates basis coefficients at sampling points (complex to complex version). + +For more details, see [`spir_sampling_eval_dd`](@ref) + +# See also + +[`spir_sampling_eval_dd`](@ref) +""" +function spir_sampling_eval_zz(s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_zz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_sampling_fit_dd(s, order, ndim, input_dims, target_dim, input, out) + +Fits values at sampling points to basis coefficients (double to double version). + +Transforms values at sampling points back to basis coefficients, where both input and output are real (double precision) values. The operation can be performed along any dimension of a multidimensional array. + +!!! note + + The output array must be pre-allocated with the correct size + +!!! note + + This function performs the inverse operation of [`spir_sampling_eval_dd`](@ref) + +!!! note + + The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency + +# Arguments + + - `s`: Pointer to the sampling object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of values at sampling points + - `out`: Output array for the fitted basis coefficients + +# Returns + +An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + +# See also + +[`spir_sampling_eval_dd`](@ref), [`spir_sampling_fit_zz`](@ref) +""" +function spir_sampling_fit_dd(s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_dd, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + s, order, ndim, input_dims, target_dim, input, out) +end + +""" + spir_sampling_fit_zz(s, order, ndim, input_dims, target_dim, input, out) + +Fits values at sampling points to basis coefficients (complex to complex version). + +For more details, see [`spir_sampling_fit_dd`](@ref) + +# See also + +[`spir_sampling_fit_dd`](@ref) +""" +function spir_sampling_fit_zz(s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_zz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) +end + +const SPIR_COMPUTATION_SUCCESS = 0 + +const SPIR_GET_IMPL_FAILED = -1 + +const SPIR_INVALID_DIMENSION = -2 + +const SPIR_INPUT_DIMENSION_MISMATCH = -3 + +const SPIR_OUTPUT_DIMENSION_MISMATCH = -4 + +const SPIR_NOT_SUPPORTED = -5 + +const SPIR_INVALID_ARGUMENT = -6 + +const SPIR_INTERNAL_ERROR = -7 + +const SPIR_STATISTICS_FERMIONIC = 1 + +const SPIR_STATISTICS_BOSONIC = 0 + +const SPIR_ORDER_COLUMN_MAJOR = 1 + +const SPIR_ORDER_ROW_MAJOR = 0 + +const SPIR_TWORK_FLOAT64 = 0 + +const SPIR_TWORK_FLOAT64X2 = 1 + +const SPIR_TWORK_AUTO = -1 + +const SPARSEIR_VERSION_MAJOR = 0 + +const SPARSEIR_VERSION_MINOR = 4 + +const SPARSEIR_VERSION_PATCH = 2 + +# exports +const PREFIXES = ["spir_", "SPIR_"] +for name in names(@__MODULE__; all=true), prefix in PREFIXES + if startswith(string(name), prefix) + @eval export $name + end +end + +end # module diff --git a/src/SparseIR.jl b/src/SparseIR.jl index 72efaeb..5d7c1b6 100644 --- a/src/SparseIR.jl +++ b/src/SparseIR.jl @@ -1,8 +1,12 @@ -""" -Intermediate representation (IR) for many-body propagators. -""" module SparseIR +include("C_API.jl") # libsparseir +using .C_API + +import LinearAlgebra +using LinearAlgebra: cond +using QuadGK: quadgk + export Fermionic, Bosonic export MatsubaraFreq, BosonicFreq, FermionicFreq, pioverbeta export FiniteTempBasis, FiniteTempBasisSet @@ -11,55 +15,22 @@ export overlap export LogisticKernel, RegularizedBoseKernel export AugmentedBasis, TauConst, TauLinear, MatsubaraConst export TauSampling, MatsubaraSampling, evaluate, fit, evaluate!, fit!, - MatsubaraSampling64F, MatsubaraSampling64B, TauSampling64, sampling_points, - basis + sampling_points, npoints +export from_IR, to_IR, npoles, get_poles, default_omega_sampling_points -using MultiFloats: MultiFloats, Float64x2, _call_big -# If we call MultiFloats.use_bigfloat_transcendentals() like MultiFloats -# recommends, we get an error during precompilation: -for name in (:exp, :sinh, :cosh, :expm1) - eval(:(function Base.$name(x::Float64x2) - Float64x2(_call_big($name, x, 107 + 20)) # 107 == precision(Float64x2) - end)) +function _is_column_major_contiguous(A::AbstractArray) + strides(A) == cumprod((1, size(A)...)[1:(end - 1)]) end -using LinearAlgebra: LinearAlgebra, cond, dot, svd, SVD, QRIteration, mul!, norm -using QuadGK: gauss, quadgk -using Bessels: sphericalbesselj -using PrecompileTools: PrecompileTools, @compile_workload - -include("_linalg.jl") -include("_roots.jl") -include("_specfuncs.jl") -using ._LinAlg: tsvd include("freq.jl") include("abstract.jl") -include("svd.jl") -include("gauss.jl") -include("poly.jl") include("kernel.jl") include("sve.jl") +include("poly.jl") include("basis.jl") -include("augment.jl") include("sampling.jl") include("dlr.jl") include("basis_set.jl") +include("augment.jl") -@static if VERSION ≥ v"1.9-" # 1.9 adds support for object caching - # Precompile - @compile_workload begin - basis = FiniteTempBasis(Fermionic(), 1e-1, 1e-1) - basis = FiniteTempBasis(Fermionic(), 1e-1, 1e-1, 1e-5) - - τ_smpl = TauSampling(basis) - iω_smpl = MatsubaraSampling(basis) - - Gτ = evaluate(τ_smpl, basis.s) - Giω = evaluate(iω_smpl, basis.s) - - fit(τ_smpl, Gτ) - fit(iω_smpl, Giω) - end -end - -end # module +end # module SparseIR diff --git a/src/_linalg.jl b/src/_linalg.jl deleted file mode 100644 index 46b3e7e..0000000 --- a/src/_linalg.jl +++ /dev/null @@ -1,316 +0,0 @@ -module _LinAlg - -using GenericLinearAlgebra: svd! -using LinearAlgebra: norm, lmul!, rmul!, triu!, Givens, I, SVD, reflector!, - reflectorApply!, QRPivoted, QRPackedQ - -export tsvd, tsvd!, svd_jacobi, svd_jacobi!, rrqr, rrqr! - -""" -Truncated rank-revealing QR decomposition with full column pivoting. - -Decomposes a `(m, n)` matrix `A` into the product: - - A[:,piv] == Q * R - -where `Q` is an `(m, k)` isometric matrix, `R` is a `(k, n)` upper -triangular matrix, `piv` is a permutation vector, and `k` is chosen such -that the relative tolerance `tol` is met in the equality above. -""" -function rrqr!(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} - # DGEQPF - m, n = size(A) - k = min(m, n) - Base.require_one_based_indexing(A) - - jpvt = collect(1:n) - taus = Vector{T}(undef, k) - - xnorms = sqrt.(dropdims(sum(abs2, A; dims=1); dims=1)) - pnorms = copy(xnorms) - sqrteps = sqrt(eps(T)) - - @inbounds for i in 1:k - pvt = argmax(@view pnorms[i:end]) + i - 1 - if i ≠ pvt - jpvt[i], jpvt[pvt] = jpvt[pvt], jpvt[i] - xnorms[pvt] = xnorms[i] - pnorms[pvt] = pnorms[i] - - swapcols!(A, i, pvt) - end - - tau_i = reflector!(@view A[i:end, i]) - taus[i] = tau_i - reflectorApply!((@view A[i:end, i]), tau_i, @view A[i:end, (i + 1):end]) - - # Lapack Working Note 176. - for j in (i + 1):n - temp = abs(A[i, j]) / pnorms[j] - temp = max(zero(T), (one(T) + temp) * (one(T) - temp)) - temp2 = temp * abs2(pnorms[j] / xnorms[j]) - if temp2 < sqrteps - recomputed = norm(@view A[(i + 1):end, j]) - pnorms[j] = recomputed - xnorms[j] = recomputed - else - pnorms[j] *= sqrt(temp) - end - end - - # Since we did pivoting, R[i,:] is bounded by R[i,i], so we can - # simply ignore all the other rows - if abs(A[i, i]) < rtol * abs(A[1, 1]) - A[i:end, i:end] .= zero(T) - taus[i:end] .= zero(T) - k = i - 1 - break - end - end - return QRPivoted(A, taus, jpvt), k -end - -""" -Truncated rank-revealing QR decomposition with full column pivoting. -""" -rrqr(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} = rrqr!(copy(A); rtol) - -function swapcols!(A::Matrix, i::Integer, j::Integer) - i == j && return A - @inbounds @simd ivdep for k in axes(A, 1) - A[k, i], A[k, j] = A[k, j], A[k, i] - end - return A -end - -""" -Truncate RRQR result low-rank -""" -function truncate_qr_result(qr::QRPivoted{T}, k::Integer) where {T} - m, n = size(qr) - 0 ≤ k ≤ min(m, n) || throw(DomainError(k, "Invalid rank, must be in [0, $(min(m, n))]")) - Qfull = QRPackedQ(view(qr.factors, :, 1:k), qr.τ[1:k]) - - Q = lmul!(Qfull, Matrix{T}(I, m, k)) - R = triu!(qr.factors[1:k, :]) - return Q, R -end - -""" -Truncated singular value decomposition. - -Decomposes an `(m, n)` matrix `A` into the product: - - A == U * (s .* VT) - -where `U` is a `(m, k)` matrix with orthogonal columns, `VT` is a `(k, n)` -matrix with orthogonal rows and `s` are the singular values, a set of `k` -nonnegative numbers in non-ascending order. The SVD is truncated in the -sense that singular values below `tol` are discarded. -""" -function tsvd!(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} - # Perform RRQR of the m x n matrix at a cost of O(m*n*k), where k is - # the QR rank (a mild upper bound for the true rank) - A_qr, k = rrqr!(A; rtol) - Q, R = truncate_qr_result(A_qr, k) - - # RRQR is an excellent preconditioner for Jacobi. One should then perform - # Jacobi on RT - RT_svd = svd!(copy(R')) - - # Reconstruct A from QR - U = Q * RT_svd.V - V = @view RT_svd.U[invperm(A_qr.p), :] - s = RT_svd.S - return SVD(U, s, V') -end - -""" -Truncated singular value decomposition. -""" -tsvd(A::AbstractMatrix{T}; rtol=eps(T)) where {T<:AbstractFloat} = tsvd!(copy(A); rtol) - -################################################################# -### Everything below is currently not used in tsvd. ### -### (GenericLinearAlgebra.svd! is used instead of svd_jacobi) ### -################################################################# - -""" -Compute Givens rotation `R` matrix that satisfies: - - [ c s ] [ f ] [ r ] - [ -s c ] [ g ] = [ 0 ] -""" -function givens_params(f::T, g::T) where {T<:AbstractFloat} - # ACM Trans. Math. Softw. 28(2), 206, Alg 1 - if iszero(g) - c, s = one(T), zero(T) - r = f - elseif iszero(f) - c, s = zero(T), T(sign(g)) - r = abs(g) - else - r = copysign(hypot(f, g), f) - c = f / r - s = g / r - end - return (c, s), r -end - -""" -Apply Givens rotation to vector: - - [ a ] = [ c s ] [ x ] - [ b ] [ -s c ] [ y ] -""" -function givens_lmul((c, s)::Tuple{T,T}, (x, y)) where {T} - a = c * x + s * y - b = c * y - s * x - return a, b -end - -""" -Perform the SVD of upper triangular two-by-two matrix: - - [ f g ] = [ cu -su ] [ smax 0 ] [ cv sv ] - [ 0 h ] [ su cu ] [ 0 smin ] [ -sv cv ] - -Note that smax and smin can be negative. -""" -function svd2x2(f::T, g::T, h::T) where {T<:AbstractFloat} - # Code taken from LAPACK xSLAV2: - fa = abs(f) - ga = abs(g) - ha = abs(h) - if fa < ha - # switch h <-> f, cu <-> sv, cv <-> su - (sv, cv), (smax, smin), (su, cu) = svd2x2(h, g, f) - elseif iszero(ga) - # already diagonal, fa > ha - smax, smin = fa, ha - cv, sv = cu, su = one(T), zero(T) - elseif fa < eps(T) * ga - # ga is very large - smax = ga - if ha > one(T) - smin = fa / (ga / ha) - else - smin = (fa / ga) * ha - end - cv, sv = f / g, one(T) - cu, su = one(T), h / g - else - # normal case - fmh = fa - ha - d = fmh / fa - q = g / f - s = T(2) - d - spq = hypot(q, s) - dpq = hypot(d, q) - a = (spq + dpq) / T(2) - smax, smin = abs(fa * a), abs(ha / a) - - tmp = (q / (spq + s) + q / (dpq + d)) * (one(T) + a) - tt = hypot(tmp, T(2)) - cv = T(2) / tt - sv = tmp / tt - cu = (cv + sv * q) / a - su = ((h / f) * sv) / a - end - return (cu, su), (smax, smin), (cv, sv) -end - -""" -Perform the SVD of an arbitrary two-by-two matrix: - - [ a11 a12 ] = [ cu -su ] [ smax 0 ] [ cv sv ] - [ a21 a22 ] [ su cu ] [ 0 smin ] [ -sv cv ] - -Note that smax and smin can be negative. -""" -function svd2x2(a11::T, a12::T, a21::T, a22::T) where {T} - abs_a12 = abs(a12) - abs_a21 = abs(a21) - if iszero(a21) - # upper triangular case - (cu, su), (smax, smin), (cv, sv) = svd2x2(a11, a12, a22) - elseif abs_a12 < abs_a21 - # closer to lower triangular - transpose matrix - (cv, sv), (smax, smin), (cu, su) = svd2x2(a11, a21, a12, a22) - else - # First, we use a givens rotation Rx - # [ cx sx ] [ a11 a12 ] = [ rx a12' ] - # [ -sx cx ] [ a21 a22 ] [ 0 a22' ] - (cx, sx), rx = givens_params(a11, a21) - a11, a21 = rx, zero(rx) - a12, a22 = givens_lmul((cx, sx), (a12, a22)) - - # Next, use the triangular routine - # [ f g ] = [ cu -su ] [ smax 0 ] [ cv sv ] - # [ 0 h ] [ su cu ] [ 0 smin ] [ -sv cv ] - (cu, su), (smax, smin), (cv, sv) = svd2x2(a11, a12, a22) - - # Finally, update the LHS (U) transform as follows: - # [ cx -sx ] [ cu -su ] = [ cu' -su' ] - # [ sx cx ] [ su cu ] [ su' cu' ] - cu, su = givens_lmul((cx, -sx), (cu, su)) - end - return (cu, su), (smax, smin), (cv, sv) -end - -function jacobi_sweep!(U::AbstractMatrix, VT::AbstractMatrix) - ii, jj = size(U) - ii ≥ jj || throw(ArgumentError("matrix must be 'tall'")) - size(VT, 1) == jj || throw(ArgumentError("U and VT must be compatible")) - Base.require_one_based_indexing(U) - Base.require_one_based_indexing(VT) - - offd = zero(eltype(U)) - @inbounds for i in 1:ii - for j in (i + 1):jj - # Construct the 2x2 matrix to be diagonalized - Hii = sum(abs2, @view U[:, i]) - Hij = sum(k -> @inbounds(U[k, i]*U[k, j]), 1:ii) - Hjj = sum(abs2, @view U[:, j]) - offd += abs2(Hij) - - # diagonalize - (_, _), (_, _), (cv, sv) = svd2x2(Hii, Hij, Hij, Hjj) - - # apply rotation to VT - rot = Givens(i, j, cv, sv) - lmul!(rot, VT) - rmul!(U, adjoint(rot)) - end - end - return sqrt(offd) -end - -""" -Singular value decomposition using Jacobi rotations. -""" -function svd_jacobi!(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where {T} - m, n = size(U) - m ≥ n || throw(ArgumentError("matrix must be 'tall'")) - Base.require_one_based_indexing(U) - - VT = Matrix(one(T) * I, n, n) - Unorm = norm(@view U[1:n, 1:n]) - for _ in 1:maxiter - offd = jacobi_sweep!(U, VT) - offd < rtol * Unorm && break - end - - s = norm.(eachcol(U)) - U ./= reshape(s, (1, :)) - return SVD(U, s, VT) -end - -""" -Singular value decomposition using Jacobi rotations. -""" -function svd_jacobi(U::AbstractMatrix{T}; rtol=eps(T), maxiter=20) where {T} - return svd_jacobi!(copy(U); rtol, maxiter) -end - -end # module _LinAlg diff --git a/src/_roots.jl b/src/_roots.jl deleted file mode 100644 index c9e18fa..0000000 --- a/src/_roots.jl +++ /dev/null @@ -1,115 +0,0 @@ -function find_all(f::F, xgrid::AbstractVector{T}) where {F,T} - fx = map(Float64 ∘ f, xgrid)#::Vector{Float64} - hit = iszero.(fx) - x_hit = xgrid[hit] - - sign_change = @views @. signbit(fx[begin:(end - 1)]) ≠ signbit(fx[(begin + 1):end]) - @. @views sign_change &= ~hit[begin:(end - 1)] & ~hit[(begin + 1):end] - any(sign_change) || return x_hit - - where_a = BitVector([sign_change; false]) - where_b = BitVector([false; sign_change]) - a = xgrid[where_a] - b = xgrid[where_b] - fa = fx[where_a] - - ϵ_x = if T <: AbstractFloat - eps(T) * maximum(abs, xgrid) - else - 0 - end - x_bisect = bisect.(f, a, b, fa, ϵ_x) - - return sort!([x_hit; x_bisect]) -end - -function bisect(f, a, b, fa, ϵ_x) - while true - mid = midpoint(a, b) - closeenough(a, mid, ϵ_x) && return mid - fmid = f(mid) - if signbit(fa) ≠ signbit(fmid) - b = mid - else - a, fa = mid, fmid - end - end -end - -closeenough(a::T, b::T, ϵ) where {T<:AbstractFloat} = isapprox(a, b; rtol=0, atol=ϵ) -closeenough(a::T, b::T, _) where {T<:Integer} = a == b - -function refine_grid(grid::Vector{T}, ::Val{α}) where {T,α} - isempty(grid) && return float(T)[] - n = length(grid) - newn = α * (n - 1) + 1 - newgrid = Vector{float(T)}(undef, newn) - - @inbounds for i in 1:(n - 1) - xb = grid[i] - xe = grid[i + 1] - Δx = (xe - xb) / α - newi = α * (i - 1) - for j in 1:α - newgrid[newi + j] = xb + Δx * (j - 1) - end - end - newgrid[end] = last(grid) - return newgrid -end - -function discrete_extrema(f::F, xgrid) where {F<:Function} - fx::Vector{Float64} = f.(xgrid) - absfx = abs.(fx) - - # Forward differences: derivativesignchange[i] now means that the secant - # changes sign fx[i+1]. This means that the extremum is STRICTLY between - # x[i] and x[i+2]. - signdfdx = signbit.(diff(fx)) - derivativesignchange = @views (signdfdx[begin:(end - 1)] .≠ signdfdx[(begin + 1):end]) - derivativesignchange_a = BitVector([derivativesignchange; false; false]) - derivativesignchange_b = BitVector([false; false; derivativesignchange]) - - a = xgrid[derivativesignchange_a] - b = xgrid[derivativesignchange_b] - absf_a = absfx[derivativesignchange_a] - absf_b = absfx[derivativesignchange_b] - res = bisect_discr_extremum.(abs ∘ f, a, b, absf_a, absf_b) - - # We consider the outer points to be extrema if there is a decrease - # in magnitude or a sign change inwards - sfx = signbit.(fx) - if absfx[begin] > absfx[begin + 1] || sfx[begin] ≠ sfx[begin + 1] - pushfirst!(res, first(xgrid)) - end - if absfx[end] > absfx[end - 1] || sfx[end] ≠ sfx[end - 1] - push!(res, last(xgrid)) - end - - return sort!(res) -end - -function bisect_discr_extremum(absf, a, b, absf_a, absf_b) - d = b - a - - d <= 1 && return ifelse(absf_a > absf_b, a, b) - d == 2 && return a + 1 - - m = midpoint(a, b) - n = m + 1 - absf_m = absf(m) - absf_n = absf(n) - - a, b, - absf_a, absf_b = ifelse(absf_m > absf_n, - (a, n, absf_a, absf_n), - (m, b, absf_m, absf_b)) - - return bisect_discr_extremum(absf, a, b, absf_a, absf_b) -end - -# This implementation of `midpoint` is performance-optimized but safe -# only if `lo <= hi`. -midpoint(lo::T, hi::T) where {T<:Integer} = lo + ((hi - lo) >>> 0x01) -midpoint(lo::T, hi::T) where {T<:AbstractFloat} = lo + ((hi - lo) * T(0.5)) -midpoint(lo, hi) = midpoint(promote(lo, hi)...) diff --git a/src/_specfuncs.jl b/src/_specfuncs.jl deleted file mode 100644 index f03eda6..0000000 --- a/src/_specfuncs.jl +++ /dev/null @@ -1,73 +0,0 @@ -# Adapted from https://github.com/numpy/numpy/blob/4adc87dff15a247e417d50f10cc4def8e1c17a03/numpy/polynomial/legendre.py#L832-L914 -function legval(x, c::AbstractVector) - nd = length(c) - nd ≥ 2 || return last(c) - - c0, c1 = @inbounds c[nd - 1], c[nd] - @inbounds for j in (nd - 2):-1:1 - k = j / (j + 1) - c0, c1 = c[j] - c1 * k, c0 + c1 * x * (k + 1) - end - return c0 + c1 * x -end - -# Adapted from https://github.com/numpy/numpy/blob/4adc87dff15a247e417d50f10cc4def8e1c17a03/numpy/polynomial/legendre.py#L1126-L1176 -""" -legvander(x, deg) - -Pseudo-Vandermonde matrix of degree `deg`. -""" -function legvander(x::AbstractVector{T}, deg::Integer) where {T} - deg ≥ 0 || throw(DomainError(deg, "degree needs to be non-negative")) - - v = Matrix{T}(undef, length(x), deg + 1) - - # Use forward recursion to generate the entries. This is not as accurate - # as reverse recursion in this application but it is more efficient. - @inbounds begin - for i in eachindex(x) - v[i, 1] = one(T) - end - if deg > 0 - for i in eachindex(x) - v[i, 2] = x[i] - end - for i in 2:deg - invi = inv(i) - @views @. v[:, i + 1] = v[:, i] * x * (2 - invi) - v[:, i - 1] * (1 - invi) - end - end - end - - return v -end - -# Adapted from https://github.com/numpy/numpy/blob/4adc87dff15a247e417d50f10cc4def8e1c17a03/numpy/polynomial/legendre.py#L612-L701 -""" - legder -""" -function legder(c::AbstractMatrix{T}, cnt=1) where {T} - cnt ≥ 0 || throw(DomainError(cnt, "The order of derivation needs to be non-negative")) - cnt == 0 && return c - - c = copy(c) - n, m = size(c) - if cnt ≥ n - return zeros(T, (1, m)) - else - @views @inbounds for _ in 1:cnt - n -= 1 - der = Matrix{T}(undef, n, m) - for j in n:-1:3 - @. der[j, :] = (2j - 1) * c[j + 1, :] - @. c[j - 1, :] += c[j + 1, :] - end - if n > 1 - @. der[2, :] = 3c[3, :] - end - @. der[1, :] = c[2, :] - c = der - end - end - return c -end diff --git a/src/abstract.jl b/src/abstract.jl index 3393415..cf9fcaa 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -20,6 +20,65 @@ where `basis.uhat[l]` is now the Fourier transform of the basis function. """ abstract type AbstractBasis{S<:Statistics} end +@doc raw""" + AbstractKernel + +Integral kernel `K(x, y)`. + +Abstract base type for an integral kernel, i.e. a AbstractFloat binary function +``K(x, y)`` used in a Fredhold integral equation of the first kind: +```math + u(x) = ∫ K(x, y) v(y) dy +``` +where ``x ∈ [x_\mathrm{min}, x_\mathrm{max}]`` and +``y ∈ [y_\mathrm{min}, y_\mathrm{max}]``. For its SVE to exist, +the kernel must be square-integrable, for its singular values to decay +exponentially, it must be smooth. + +In general, the kernel is applied to a scaled spectral function ``ρ'(y)`` as: +```math + ∫ K(x, y) ρ'(y) dy, +``` +where ``ρ'(y) = w(y) ρ(y)``. +""" +abstract type AbstractKernel end + +abstract type AbstractReducedKernel <: AbstractKernel end + +############################################################################### + +""" + AbstractSVEHints + +Discretization hints for singular value expansion of a given kernel. +""" +abstract type AbstractSVEHints end + +############################################################################### + +""" + AbstractSampling + +Abstract type for sparse sampling. + +Encodes the "basis transformation" of a propagator from the truncated IR +basis coefficients `G_ir[l]` to time/frequency sampled on sparse points +`G(x[i])` together with its inverse, a least squares fit: + + ________________ ___________________ + | | evaluate | | + | Basis |---------------->| Value on | + | coefficients |<----------------| sampling points | + |________________| fit |___________________| +""" +abstract type AbstractSampling{T,Tmat,F} end + +############################################################################### + +abstract type AbstractSVE end + +_get_ptr(basis::AbstractBasis) = basis.ptr + Base.broadcastable(b::AbstractBasis) = Ref(b) Base.firstindex(::AbstractBasis) = 1 Base.length(basis::AbstractBasis) = length(basis.s) @@ -45,6 +104,34 @@ For the IR basis, we simply have that `σ[i] = s[i] / first(s)`. """ function significance end +""" + s(basis::AbstractBasis) + +Get the singular values of the basis. +""" +function s end + +""" + u(basis::AbstractBasis) + +Get the u basis functions (imaginary time). +""" +function u end + +""" + v(basis::AbstractBasis) + +Get the v basis functions (real frequency). +""" +function v end + +""" + uhat(basis::AbstractBasis) + +Get the uhat basis functions (Matsubara frequency). +""" +function uhat end + """ default_tau_sampling_points(basis::AbstractBasis) @@ -94,7 +181,9 @@ const wmax = ωmax β(basis::AbstractBasis) beta(basis::AbstractBasis) -Inverse temperature or `nothing` if unscaled basis. +Inverse temperature of the basis. + +Returns the inverse temperature parameter β used in the basis construction. """ β(basis::AbstractBasis) = basis.β const beta = β @@ -108,65 +197,15 @@ iswellconditioned(::AbstractBasis) = true ############################################################################### -@doc raw""" - AbstractKernel - -Integral kernel `K(x, y)`. - -Abstract base type for an integral kernel, i.e. a AbstractFloat binary function -``K(x, y)`` used in a Fredhold integral equation of the first kind: -```math - u(x) = ∫ K(x, y) v(y) dy -``` -where ``x ∈ [x_\mathrm{min}, x_\mathrm{max}]`` and -``y ∈ [y_\mathrm{min}, y_\mathrm{max}]``. For its SVE to exist, -the kernel must be square-integrable, for its singular values to decay -exponentially, it must be smooth. - -In general, the kernel is applied to a scaled spectral function ``ρ'(y)`` as: -```math - ∫ K(x, y) ρ'(y) dy, -``` -where ``ρ'(y) = w(y) ρ(y)``. -""" -abstract type AbstractKernel end - -abstract type AbstractReducedKernel <: AbstractKernel end - Base.broadcastable(kernel::AbstractKernel) = Ref(kernel) -############################################################################### - -""" - AbstractSVEHints - -Discretization hints for singular value expansion of a given kernel. -""" -abstract type AbstractSVEHints end - -############################################################################### - -""" - AbstractSampling - -Abstract type for sparse sampling. - -Encodes the "basis transformation" of a propagator from the truncated IR -basis coefficients `G_ir[l]` to time/frequency sampled on sparse points -`G(x[i])` together with its inverse, a least squares fit: - - ________________ ___________________ - | | evaluate | | - | Basis |---------------->| Value on | - | coefficients |<----------------| sampling points | - |________________| fit |___________________| -""" -abstract type AbstractSampling{T,Tmat,F} end - Base.broadcastable(sampling::AbstractSampling) = Ref(sampling) function LinearAlgebra.cond(sampling::AbstractSampling) - first(sampling.matrix_svd.S) / last(sampling.matrix_svd.S) + cond_num = Ref{Float64}(-1.0) + status = spir_sampling_get_cond_num(sampling.ptr, cond_num) + status == SPIR_COMPUTATION_SUCCESS || error("Failed to get condition number: $status") + return cond_num[] end """ @@ -190,7 +229,3 @@ function Base.show(io::IO, ::MIME"text/plain", smpl::S) where {S<:AbstractSampli end print(io, " $(last(sampling_points(smpl)))") end - -############################################################################### - -abstract type AbstractSVE end diff --git a/src/augment.jl b/src/augment.jl index 7da7715..0679997 100644 --- a/src/augment.jl +++ b/src/augment.jl @@ -59,6 +59,49 @@ struct AugmentedBasis{S<:Statistics,B<:FiniteTempBasis{S},A<:AugmentationTuple,F uhat :: FHAT end +function TauSampling(basis::AugmentedBasis{S}; + sampling_points=default_tau_sampling_points(basis)) where {S} + matrix = eval_matrix(TauSampling, basis, sampling_points) + status = Ref{Int32}(-100) + ptr = C_API.spir_tau_sampling_new_with_matrix( + C_API.SPIR_ORDER_COLUMN_MAJOR, _statistics_to_c(S), length(basis), + length(sampling_points), sampling_points, matrix, status) + status[] == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to create tau sampling: status=$(status[])") + ptr != C_NULL || error("Failed to create tau sampling: null pointer returned") + + return TauSampling{Float64,typeof(basis)}(ptr, sampling_points, basis) +end + +function MatsubaraSampling( + basis::AugmentedBasis{S}; + positive_only=false, + sampling_points=default_matsubara_sampling_points(basis; positive_only) +) where {S} + pts = MatsubaraFreq.(sampling_points) + matrix = eval_matrix(MatsubaraSampling, basis, pts) + status = Ref{Int32}(-100) + ptr = C_API.spir_matsu_sampling_new_with_matrix( + C_API.SPIR_ORDER_COLUMN_MAJOR, + _statistics_to_c(S), + length(basis), + positive_only, + length(sampling_points), + sampling_points, + matrix, + status + ) + status[] == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to create Matsubara sampling: status=$(status[])") + ptr != C_NULL || error("Failed to create Matsubara sampling: null pointer returned") + + return MatsubaraSampling{eltype(pts),typeof(basis)}(ptr, pts, positive_only, basis) +end + +function _get_ptr(basis::AugmentedBasis) + _get_ptr(basis.basis) +end + function AugmentedBasis(basis::AbstractBasis, augmentations...) augs = create.(augmentations, basis) u = AugmentedTauFunction(basis.u, augs) @@ -84,11 +127,29 @@ accuracy(basis::AugmentedBasis) = accuracy(basis.basis) significance(basis::AugmentedBasis) = vcat(ones(naug(basis)), significance(basis.basis)) function default_tau_sampling_points(basis::AugmentedBasis) - x = default_sampling_points(basis.basis.sve_result.u, length(basis)) - β(basis) / 2 * (x .+ 1) + points = Vector{Float64}(undef, length(basis)) + n_points_returned = Ref{Cint}(0) + status = spir_basis_get_default_taus_ext( + _get_ptr(basis.basis), length(basis), points, n_points_returned) + status == SPIR_COMPUTATION_SUCCESS || error("Failed to get default tau sampling points") + return points end + function default_matsubara_sampling_points(basis::AugmentedBasis; positive_only=false) - default_matsubara_sampling_points(basis.basis.uhat_full, length(basis); positive_only) + n_points = Ref{Cint}(0) + status = spir_basis_get_n_default_matsus_ext( + _get_ptr(basis.basis), positive_only, length(basis), n_points) + status == SPIR_COMPUTATION_SUCCESS || + error("Failed to get number of default Matsubara sampling points") + points = Vector{Int64}(undef, n_points[]) + n_points_returned = Ref{Cint}(0) + status = spir_basis_get_default_matsus_ext( + _get_ptr(basis.basis), positive_only, length(basis), points, n_points_returned) + status == SPIR_COMPUTATION_SUCCESS || + error("Failed to get default Matsubara sampling points") + n_points_returned[] == n_points[] || + error("n_points_returned=$(n_points_returned[]) != n_points=$(n_points[])") + return points end function iswellconditioned(basis::AugmentedBasis) @@ -122,6 +183,7 @@ function (a::AbstractAugmentedFunction)(x) faug_x = [faug_l(x) for faug_l in faug(a)] return vcat(faug_x, fbasis_x) end + function (a::AbstractAugmentedFunction)(x::AbstractArray) fbasis_x = fbasis(a)(x) faug_x = reduce(vcat, faug_l.(reshape(x, (1, :))) for faug_l in faug(a)) @@ -147,8 +209,9 @@ augmentedfunction(aτ::AugmentedTauFunction) = aτ.a AugmentedTauFunction(fbasis, faug) = AugmentedTauFunction(AugmentedFunction(fbasis, faug)) -xmin(aτ::AugmentedTauFunction) = xmin(fbasis(aτ)) -xmax(aτ::AugmentedTauFunction) = xmax(fbasis(aτ)) +# Not supported yet +#xmin(aτ::AugmentedTauFunction) = xmin(fbasis(aτ)) +#xmax(aτ::AugmentedTauFunction) = xmax(fbasis(aτ)) function deriv(aτ::AugmentedTauFunction, n=Val(1)) dbasis = PiecewiseLegendrePolyVector(deriv.(fbasis(aτ), n)) @@ -190,7 +253,10 @@ end create(::Type{TauConst}, basis::AbstractBasis{Bosonic}) = TauConst(β(basis)) function (aug::TauConst)(τ) - 0 ≤ τ ≤ β(aug) || throw(DomainError(τ, "τ must be in [0, β].")) + #0 ≤ τ ≤ β(aug) || throw(DomainError(τ, "τ must be in [0, β].")) + -β(aug) / 2 ≤ τ ≤ β(aug) / 2 || + throw(DomainError(τ, "τ must be in [-β(aug)/2, β(aug)/2].")) + return 1 / sqrt(β(aug)) end function (aug::TauConst)(n::BosonicFreq) @@ -222,7 +288,9 @@ end create(::Type{TauLinear}, basis::AbstractBasis{Bosonic}) = TauLinear(β(basis)) function (aug::TauLinear)(τ) - 0 ≤ τ ≤ β(aug) || throw(DomainError(τ, "τ must be in [0, β].")) + # 0 ≤ τ ≤ β(aug) || throw(DomainError(τ, "τ must be in [0, β].")) + -β(aug) / 2 ≤ τ ≤ β(aug) / 2 || + throw(DomainError(τ, "τ must be in [-β(aug)/2, β(aug)/2].")) x = 2 / β(aug) * τ - 1 return aug.norm * x end @@ -255,9 +323,11 @@ end create(::Type{MatsubaraConst}, basis::AbstractBasis) = MatsubaraConst(β(basis)) function (aug::MatsubaraConst)(τ) - 0 ≤ τ ≤ β(aug) || throw(DomainError(τ, "τ must be in [0, β].")) + -β(aug) / 2 ≤ τ ≤ β(aug) / 2 || + throw(DomainError(τ, "τ must be in [-β(aug)/2, β(aug)/2].")) return NaN end + function (aug::MatsubaraConst)(::MatsubaraFreq) return one(β(aug)) end diff --git a/src/basis.jl b/src/basis.jl index 88445be..3db04e5 100644 --- a/src/basis.jl +++ b/src/basis.jl @@ -22,7 +22,7 @@ the variables. points `x`, you can call the function `u(x)`. To obtain a single basis function, a slice or a subset `l`, you can use `u[l]`. - - `uhat::PiecewiseLegendreFT`: + - `uhat::PiecewiseLegendreFTVector`: Set of IR basis functions on the Matsubara frequency (`wn`) axis. These objects are stored as a set of Bessel functions. @@ -32,7 +32,7 @@ the variables. numbers for bosonic/fermionic objects. To obtain a single basis function, a slice or a subset `l`, you can use `uhat[l]`. - `s`: Vector of singular values of the continuation kernel - - `v::PiecewiseLegendrePoly`: + - `v::PiecewiseLegendrePolyVector`: Set of IR basis functions on the real frequency (`w`) axis. These functions are stored as piecewise Legendre polynomials. @@ -40,250 +40,207 @@ the variables. points `w`, you can call the function `v(w)`. To obtain a single basis function, a slice or a subset `l`, you can use `v[l]`. """ -struct FiniteTempBasis{S,K} <: AbstractBasis{S} - kernel :: K - sve_result :: SVEResult{K} - accuracy :: Float64 - β :: Float64 - u :: PiecewiseLegendrePolyVector - v :: PiecewiseLegendrePolyVector - s :: Vector{Float64} - uhat :: PiecewiseLegendreFTVector{S} - uhat_full :: PiecewiseLegendreFTVector{S} +mutable struct FiniteTempBasis{S,K} <: AbstractBasis{S} + ptr::Ptr{spir_basis} + kernel::K + sve_result::SVEResult{K} + beta::Float64 + wmax::Float64 + epsilon::Float64 + s::Vector{Float64} + u::PiecewiseLegendrePolyVector + v::PiecewiseLegendrePolyVector + uhat::PiecewiseLegendreFTVector + function FiniteTempBasis{S}(kernel::K, sve_result::SVEResult{K}, β::Real, ωmax::Real, + ε::Real, max_size::Int) where {S<:Statistics,K<:AbstractKernel} + # Validate kernel/statistics compatibility + if isa(kernel, RegularizedBoseKernel) && S === Fermionic + throw(ArgumentError("RegularizedBoseKernel is incompatible with Fermionic statistics")) + end + + # Create basis + status = Ref{Int32}(-100) + basis = SparseIR.spir_basis_new( + _statistics_to_c(S), β, ωmax, kernel.ptr, sve_result.ptr, max_size, status) + status[] == SparseIR.SPIR_COMPUTATION_SUCCESS || + error("Failed to create FiniteTempBasis $S $K $β $ωmax $ε $max_size $status[]") + + basis_size = Ref{Int32}(0) + spir_basis_get_size(basis, basis_size) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis size") + s = Vector{Float64}(undef, Int(basis_size[])) + spir_basis_get_svals(basis, s) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get singular values") + u_status = Ref{Int32}(-100) + u = spir_basis_get_u(basis, u_status) + u_status[] == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis functions u $u_status[]") + v_status = Ref{Int32}(-100) + v = spir_basis_get_v(basis, v_status) + v_status[] == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis functions v $v_status[]") + uhat_status = Ref{Int32}(-100) + uhat = spir_basis_get_uhat(basis, uhat_status) + uhat_status[] == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis functions uhat $uhat_status[]") + result = new{S,K}( + basis, kernel, sve_result, Float64(β), Float64(ωmax), Float64(ε), + s, + PiecewiseLegendrePolyVector(u, 0.0, β), + PiecewiseLegendrePolyVector(v, -ωmax, ωmax), + PiecewiseLegendreFTVector(uhat) + ) + finalizer(b -> spir_basis_release(b.ptr), result) + return result + end end """ - FiniteTempBasis{S}(β, ωmax, ε=nothing; max_size=nothing, args...) + FiniteTempBasis{S}(β, ωmax, ε; kernel=LogisticKernel(β * ωmax), sve_result=SVEResult(kernel, ε), max_size=-1) Construct a finite temperature basis suitable for the given `S` (`Fermionic` or `Bosonic`) and cutoffs `β` and `ωmax`. + +# Arguments + + - `β`: Inverse temperature (must be positive) + - `ωmax`: Frequency cutoff (must be non-negative) + - `ε`: This parameter controls the number of basis functions. Only singular values ≥ ε * s[1] are kept. + Typical values are 1e-6 to 1e-12 depending on the desired accuracy for your calculations. If ε is smaller than the square root of double precision machine epsilon (≈ 1.49e-8), the library will automatically use higher precision for the singular value decomposition, resulting in longer computation time for basis generation. + +The number of basis functions grows logarithmically as log(1/ε) log (β * ωmax). """ -function FiniteTempBasis{S}(β::Real, ωmax::Real, ε=nothing; max_size=nothing, - kernel=LogisticKernel(β * ωmax), - sve_result=SVEResult(kernel; ε)) where {S} - FiniteTempBasis(S(), β, ωmax, ε; max_size, kernel, sve_result) +function FiniteTempBasis{S}(β::Real, ωmax::Real, ε::Real; kernel=LogisticKernel(β * ωmax), + sve_result=SVEResult(kernel, ε), max_size=-1) where {S<:Statistics} + FiniteTempBasis{S}(kernel, sve_result, Float64(β), Float64(ωmax), Float64(ε), max_size) end -function FiniteTempBasis(statistics::Statistics, β::Real, ωmax::Real, ε=nothing; - max_size=nothing, kernel=LogisticKernel(β * ωmax), - sve_result=SVEResult(kernel; ε)) - β > 0 || throw(DomainError(β, "Inverse temperature β must be positive")) - ωmax ≥ 0 || throw(DomainError(ωmax, "Frequency cutoff ωmax must be non-negative")) +""" + FiniteTempBasis(stat::Statistics, β, ωmax, ε; kernel=LogisticKernel(β * ωmax), sve_result=SVEResult(kernel, ε), max_size=-1) - u_, s_, v_ = part(sve_result; ε, max_size) +Convenience constructor that matches SparseIR.jl signature. - accuracy = if length(sve_result.s) > length(s_) - sve_result.s[length(s_) + 1] / first(sve_result.s) - else - last(sve_result.s) / first(sve_result.s) - end +Construct a finite temperature basis for the given statistics type and cutoffs. - # The polynomials are scaled to the new variables by transforming the - # knots according to: tau = β/2 * (x + 1), w = ωmax * y. Scaling - # the data is not necessary as the normalization is inferred. - ωmax = Λ(kernel) / β - u_knots = (β / 2) .* (knots(u_) .+ 1) - v_knots = ωmax .* knots(v_) - u = PiecewiseLegendrePolyVector(u_, u_knots; Δx=(β / 2) .* Δx(u_), symm=symm(u_)) - v = PiecewiseLegendrePolyVector(v_, v_knots; Δx=ωmax .* Δx(v_), symm=symm(v_)) - - # The singular values are scaled to match the change of variables, with - # the additional complexity that the kernel may have an additional - # power of w. - s = (sqrt(β / 2 * ωmax) * ωmax^(-ypower(kernel))) .* s_ - - # HACK: as we don't yet support Fourier transforms on anything but the - # unit interval, we need to scale the underlying data. - û_base_full = PiecewiseLegendrePolyVector(sqrt(β) .* data(sve_result.u), sve_result.u) - û_full = PiecewiseLegendreFTVector(û_base_full, statistics; - n_asymp=conv_radius(kernel)) - û = û_full[1:length(s)] - - return FiniteTempBasis(kernel, sve_result, accuracy, float(β), u, v, s, û, û_full) -end +# Arguments -function Base.show(io::IO, ::MIME"text/plain", b::FiniteTempBasis) - print(io, "$(length(b))-element FiniteTempBasis{$(typeof(statistics(b)))} with ") - println(io, "β = $(β(b)), ωmax = $(ωmax(b)) and singular values:") - for s in b.s[begin:(end - 1)] - println(io, " $s") - end - print(io, " $(last(b.s))") -end + - `stat`: Statistics type (`Fermionic()` or `Bosonic()`) + - `β`: Inverse temperature (must be positive) + - `ωmax`: Frequency cutoff (must be non-negative) + - `ε`: Accuracy target for the basis. This parameter controls the number of basis functions. Only singular values ≥ ε * s[1] are kept. + Typical values are 1e-6 to 1e-12 depending on the desired accuracy for your calculations. If ε is smaller than the square root of double precision machine epsilon (≈ 1.49e-8), the library will automatically use higher precision for the singular value decomposition, resulting in longer computation time for basis generation. -function Base.getindex(basis::FiniteTempBasis, i::AbstractRange) - FiniteTempBasis(statistics(basis), β(basis), ωmax(basis), nothing; - max_size=range_to_length(i), kernel=basis.kernel, - sve_result=basis.sve_result) +The number of basis functions grows logarithmically as log(1/ε) log (β * ωmax). +""" +function FiniteTempBasis( + stat::S, β::Real, ωmax::Real, ε::Real; kernel=LogisticKernel(β * ωmax), + sve_result=SVEResult(kernel, ε), max_size=-1) where {S<:Statistics} + FiniteTempBasis{typeof(stat)}(β, ωmax, ε; kernel, sve_result, max_size) end -significance(basis::FiniteTempBasis) = basis.s ./ first(basis.s) -accuracy(basis::FiniteTempBasis) = basis.accuracy -ωmax(basis::FiniteTempBasis) = Λ(basis) / β(basis) -sve_result(basis::FiniteTempBasis) = basis.sve_result -kernel(basis::FiniteTempBasis) = basis.kernel -Λ(basis::FiniteTempBasis) = Λ(kernel(basis)) - function default_tau_sampling_points(basis::FiniteTempBasis) - x = default_sampling_points(basis.sve_result.u, length(basis)) - β(basis) / 2 * (x .+ 1) + n_points = Ref{Int32}(-1) + ret = spir_basis_get_n_default_taus(basis.ptr, n_points) + ret == SPIR_COMPUTATION_SUCCESS || error("Failed to get number of default tau points") + points_array = Vector{Float64}(undef, n_points[]) + ret = spir_basis_get_default_taus(basis.ptr, points_array) + return points_array end + function default_matsubara_sampling_points(basis::FiniteTempBasis; positive_only=false) - default_matsubara_sampling_points(basis.uhat_full, length(basis); positive_only) + n_points = Ref{Int32}(0) + ret = spir_basis_get_n_default_matsus(basis.ptr, positive_only, n_points) + ret == SPIR_COMPUTATION_SUCCESS || + error("Failed to get number of default matsubara points") + n_points[] > 0 || error("No default matsubara points found") + + points_array = Vector{Int64}(undef, n_points[]) + ret = spir_basis_get_default_matsus(basis.ptr, positive_only, points_array) + ret == SPIR_COMPUTATION_SUCCESS || error("Failed to get default matsubara points") + return points_array end + function default_omega_sampling_points(basis::FiniteTempBasis) - y = default_sampling_points(basis.sve_result.v, length(basis)) - ωmax(basis) * y + n_points = Ref{Int32}(-1) + ret = spir_basis_get_n_default_ws(basis.ptr, n_points) + ret == SPIR_COMPUTATION_SUCCESS || error("Failed to get number of default omega points") + points_array = Vector{Float64}(undef, n_points[]) + ret = spir_basis_get_default_ws(basis.ptr, points_array) + return points_array end -""" - rescale(basis::FiniteTempBasis, new_β) +# Basis function type +mutable struct BasisFunction + ptr::Ptr{spir_funcs} + basis::FiniteTempBasis # Keep reference to prevent GC +end -Return a basis for different temperature. +# Property accessors +β(basis::FiniteTempBasis) = basis.beta +ωmax(basis::FiniteTempBasis) = basis.wmax +Λ(basis::FiniteTempBasis) = basis.beta * basis.wmax -Uses the same kernel with the same ``ε``, but a different -temperature. Note that this implies a different UV cutoff ``ωmax``, -since ``Λ == β * ωmax`` stays constant. -""" -function rescale(basis::FiniteTempBasis, new_β) - new_ωmax = Λ(kernel(basis)) / new_β - return FiniteTempBasis(statistics(basis), new_β, new_ωmax, nothing; - max_size=length(basis), kernel=kernel(basis), - sve_result=sve_result(basis)) +# For now, accuracy is approximated by epsilon +# In reality, it would be computed from the singular values +accuracy(basis::FiniteTempBasis) = basis.epsilon + +function (f::BasisFunction)(freq::MatsubaraFreq) + return f(freq.n) end """ - finite_temp_bases(β::Real, ωmax::Real, ε=nothing; - kernel=LogisticKernel(β * ωmax), sve_result=SVEResult(kernel; ε)) + rescale(basis::FiniteTempBasis, new_beta) -Construct `FiniteTempBasis` objects for fermion and bosons using the same -`LogisticKernel` instance. -""" -function finite_temp_bases(β::Real, ωmax::Real, ε=nothing; - kernel=LogisticKernel(β * ωmax), - sve_result=SVEResult(kernel; ε)) - basis_f = FiniteTempBasis{Fermionic}(β, ωmax, ε; sve_result, kernel) - basis_b = FiniteTempBasis{Bosonic}(β, ωmax, ε; sve_result, kernel) - return basis_f, basis_b -end +Return a basis for different temperature. -function default_sampling_points(u::PiecewiseLegendrePolyVector, L::Integer) - (xmin(u), xmax(u)) == (-1, 1) || error("Expecting unscaled functions here.") - - # For orthogonal polynomials (the high-T limit of IR), we know that the - # ideal sampling points for a basis of size L are the roots of the L-th - # polynomial. We empirically find that these stay good sampling points - # for our kernels (probably because the kernels are totally positive). - if L < length(u) - x₀ = roots(u[L + 1]) - else - # If we do not have enough polynomials in the basis, we approximate the - # roots of the L'th polynomial by the extrema of the last basis - # function, which is sensible due to the strong interleaving property - # of these functions' roots. - poly = last(u) - maxima = roots(deriv(poly)) - - # Putting the sampling points right at [0, β], which would be the - # local extrema, is slightly worse conditioned than putting it in the - # middle. This can be understood by the fact that the roots never - # occur right at the border. - left = (first(maxima) + xmin(poly)) / 2 - right = (last(maxima) + xmax(poly)) / 2 - x₀ = [left; maxima; right] - end +Creates a new basis with the same accuracy ``ε`` but different temperature. +The new kernel is constructed with the same cutoff parameter ``Λ = β * ωmax``, +which implies a different UV cutoff ``ωmax`` since ``Λ`` stays constant. - length(x₀) == L || @warn """ - Expecting to get $L sampling points for corresponding basis function, - instead got $(length(x₀)). This may happen if not enough precision is - left in the polynomial. - """ - return x₀ -end +# Arguments -function default_matsubara_sampling_points(û::PiecewiseLegendreFTVector, L::Integer; - fence=false, positive_only=false) - l_requested = L - - # The number of sign changes is always odd for bosonic basis and even for fermionic - # basis. So in order to get at least as many sign changes as basis functions: - statistics(û) isa Fermionic && isodd(l_requested) && (l_requested += 1) - statistics(û) isa Bosonic && iseven(l_requested) && (l_requested += 1) - - # As with the zeros, the sign changes provide excellent sampling points - if l_requested < length(û) - ωn = sign_changes(û[l_requested + 1]; positive_only) - else - # As a fallback, use the (discrete) extrema of the corresponding - # highest-order basis function in Matsubara. This turns out to be okay. - ωn = find_extrema(last(û); positive_only) - - # For bosonic bases, we must explicitly include the zero frequency, - # otherwise the condition number blows up. - if statistics(û) isa Bosonic - pushfirst!(ωn, 0) - sort!(ωn) - unique!(ωn) - end - end - - expected_size = l_requested - positive_only && (expected_size = div(expected_size + 1, 2)) + - `basis`: The original basis to rescale + - `new_beta`: New inverse temperature - length(ωn) == expected_size || @warn """ - Requesting $expected_size $(statistics(û)) sampling frequencies for basis size - L = $L, but $(length(ωn)) were returned. This may indicate a problem with precision. - """ +# Returns - fence && fence_matsubara_sampling!(ωn, positive_only) - return ωn +A new `FiniteTempBasis` with the same statistics type and accuracy but different temperature. +""" +function rescale(basis::FiniteTempBasis{S}, new_beta::Real) where {S} + # Rescale basis to new temperature + new_lambda = Λ(basis) * new_beta / β(basis) + kernel = LogisticKernel(new_lambda) + return FiniteTempBasis{S}(kernel, new_beta, ωmax(basis), accuracy(basis)) end -function fence_matsubara_sampling!(ωn::Vector{<:MatsubaraFreq}, positive_only) - # While the condition number for sparse sampling in tau saturates at a - # modest level, the conditioning in Matsubara steadily deteriorates due - # to the fact that we are not free to set sampling points continuously. - # At double precision, tau sampling is better conditioned than iwn - # by a factor of ~4 (still OK). To battle this, we fence the largest - # frequency with two carefully chosen oversampling points, which brings - # the two sampling problems within a factor of 2. - outer_frequencies = positive_only ? (last(ωn),) : (first(ωn), last(ωn)) - for ωn_outer in outer_frequencies - ωn_diff = BosonicFreq(2 * round(Int, 0.025 * Int(ωn_outer))) - length(ωn) ≥ 20 && push!(ωn, ωn_outer - sign(ωn_outer) * ωn_diff) - length(ωn) ≥ 42 && push!(ωn, ωn_outer + sign(ωn_outer) * ωn_diff) - end - return unique!(ωn) -end +# Additional utility functions +significance(basis::FiniteTempBasis) = basis.s ./ first(basis.s) function range_to_length(range::UnitRange) isone(first(range)) || error("Range must start at 1.") return last(range) end -# """ -# evaluate(basis::FiniteTempBasis, a::Vector, τ::Real) - -# Compute ``\\sum_l a_l U_l(τ)``. -# """ -# function evaluate(basis::FiniteTempBasis, a::Vector, τ::Real) -# length(basis.u) == length(a) || error("wrong number of coefficients") -# 0 <= τ <= basis.β || error("τ is out of bounds") -# sum(zip(a, basis.u); init=zero(eltype(a))) do (aₗ, uₗ) -# aₗ * uₗ(τ) -# end -# end - -# """ -# evaluate(basis::FiniteTempBasis{S}, a::Vector, iω::MatsubaraFreq{S}) where {S} - -# Compute ``\\sum_l a_l \\hat{U}_l(iω)``. -# """ -# function evaluate(basis::FiniteTempBasis{S}, a::Vector, iω::MatsubaraFreq{S}) where {S} -# length(basis.uhat) == length(a) || error("wrong number of coefficients") -# û_iω = basis.uhat(iω) -# û_iω .*= a -# sum(û_iω) -# end - -# evaluate(basis::FiniteTempBasis, a::Vector, x::AbstractArray) = evaluate.(basis, Ref(a), x) +""" + finite_temp_bases(β::Real, ωmax::Real, ε; + kernel=LogisticKernel(β * ωmax), sve_result=SVEResult(kernel, ε)) + +Construct `FiniteTempBasis` objects for fermion and bosons using the same +`LogisticKernel` instance. + +# Arguments + + - `β`: Inverse temperature (must be positive) + - `ωmax`: Frequency cutoff (must be non-negative) + - `ε`: This parameter controls the number of basis functions. Only singular values ≥ ε * s[1] are kept. + Typical values are 1e-6 to 1e-12 depending on the desired accuracy for your calculations. If ε is smaller than the square root of double precision machine epsilon (≈ 1.49e-8), the library will automatically use higher precision for the singular value decomposition, resulting in longer computation time for basis generation. + +The number of basis functions grows logarithmically as log(1/ε) log (β * ωmax). +""" +function finite_temp_bases(β::Real, ωmax::Real, ε::Real; + kernel=LogisticKernel(β * ωmax), + sve_result=SVEResult(kernel, ε)) + basis_f = FiniteTempBasis{Fermionic}(β, ωmax, ε; sve_result, kernel) + basis_b = FiniteTempBasis{Bosonic}(β, ωmax, ε; sve_result, kernel) + return basis_f, basis_b +end diff --git a/src/basis_set.jl b/src/basis_set.jl index a07373d..2a799f5 100644 --- a/src/basis_set.jl +++ b/src/basis_set.jl @@ -25,10 +25,10 @@ and associated sparse-sampling objects. - ωmax::Float64: Cut-off frequency """ struct FiniteTempBasisSet - basis_f :: FiniteTempBasis{Fermionic,LogisticKernel} - basis_b :: FiniteTempBasis{Bosonic,LogisticKernel} - smpl_tau_f :: TauSampling64 - smpl_tau_b :: TauSampling64 + basis_f :: FiniteTempBasis{Fermionic} + basis_b :: FiniteTempBasis{Bosonic} + smpl_tau_f :: TauSampling64F + smpl_tau_b :: TauSampling64B smpl_wn_f :: MatsubaraSampling64F smpl_wn_b :: MatsubaraSampling64B @@ -38,8 +38,8 @@ struct FiniteTempBasisSet Create basis sets for fermion and boson and associated sampling objects. Fermion and bosonic bases are constructed by SVE of the logistic kernel. """ - function FiniteTempBasisSet(β::Real, ωmax::Real, ε=nothing; - sve_result=SVEResult(LogisticKernel(β * ωmax); ε)) + function FiniteTempBasisSet(β::Real, ωmax::Real, ε::Real; + sve_result=SVEResult(LogisticKernel(β * ωmax), ε)) basis_f = FiniteTempBasis{Fermionic}(β, ωmax, ε; sve_result) basis_b = FiniteTempBasis{Bosonic}(β, ωmax, ε; sve_result) diff --git a/src/dlr.jl b/src/dlr.jl index 5f3c4e8..6c1baf8 100644 --- a/src/dlr.jl +++ b/src/dlr.jl @@ -1,130 +1,224 @@ -struct MatsubaraPoles{S<:Statistics} - β::Float64 +""" + DiscreteLehmannRepresentation{S,B} <: AbstractBasis{S} + +Discrete Lehmann representation (DLR) with poles selected according to extrema of IR. + +This type wraps the C API DLR functionality. The DLR basis is a variant of the IR basis +that uses a "sketching" approach - representing functions as a linear combination of +poles on the real-frequency axis: + + G(iv) == sum(a[i] / (iv - w[i]) for i in 1:npoles) + +# Fields + + - `ptr::Ptr{spir_basis}`: Pointer to the C DLR object + - `basis::B`: The underlying IR basis + - `poles::Vector{Float64}`: Pole locations on the real-frequency axis +""" +mutable struct DiscreteLehmannRepresentation{S<:Statistics,B<:AbstractBasis{S}} <: + AbstractBasis{S} + ptr::Ptr{spir_basis} + basis::B poles::Vector{Float64} - function MatsubaraPoles{S}(β::Real, poles::Vector{<:Real}) where {S<:Statistics} - new{S}(β, poles) + + function DiscreteLehmannRepresentation{S,B}(ptr::Ptr{spir_basis}, basis::B, + poles::Vector{Float64}) where {S<:Statistics,B<:AbstractBasis{S}} + obj = new{S,B}(ptr, basis, poles) + finalizer(s -> spir_basis_release(s.ptr), obj) + return obj end end -function MatsubaraPoles(stats::Statistics, β::Real, poles::Vector{<:Real}) - MatsubaraPoles{typeof(stats)}(β, poles) -end +""" + DiscreteLehmannRepresentation(basis::AbstractBasis, poles=nothing) -(mp::MatsubaraPoles{Fermionic})(n::FermionicFreq) = 1 ./ (valueim(n, mp.β) .- mp.poles) -function (mp::MatsubaraPoles{Bosonic})(n::BosonicFreq) - tanh.(mp.β / 2 .* mp.poles) ./ (valueim(n, mp.β) .- mp.poles) -end +Construct a DLR basis from an IR basis. -function (mp::MatsubaraPoles{S})(n::AbstractVector{MatsubaraFreq{S}}) where {S} - mapreduce(mp, hcat, n) +If `poles` is not provided, uses the default omega sampling points from the IR basis. +""" +function DiscreteLehmannRepresentation( + basis::AbstractBasis, poles=default_omega_sampling_points(basis)) + status = Ref{Int32}(-100) + dlr_ptr = C_API.spir_dlr_new_with_poles(basis.ptr, length(poles), poles, status) + status[] == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to create DLR with poles: status=$(status[])") + dlr_ptr != C_NULL || error("Failed to create DLR with poles: null pointer returned") + return DiscreteLehmannRepresentation{typeof(statistics(basis)),typeof(basis)}( + dlr_ptr, basis, poles) end -(mp::MatsubaraPoles)(n::AbstractVector{<:Integer}) = mp(MatsubaraFreq.(n)) -struct TauPoles{S<:Statistics} - β::Float64 - poles::Vector{Float64} - ωmax::Float64 - function TauPoles{S}(β::Real, poles::Vector{<:Real}) where {S<:Statistics} - new{S}(β, poles, maximum(abs, poles)) +""" + from_IR(dlr::DiscreteLehmannRepresentation, gl::Array, dims=1) + +Transform from IR basis coefficients to DLR coefficients. + +# Arguments + + - `dlr`: The DLR basis + - `gl`: IR basis coefficients + - `dims`: Dimension along which the basis coefficients are stored + +# Returns + +DLR coefficients with the same shape as input, but with size `length(dlr)` along dimension `dims`. +""" +function from_IR(dlr::DiscreteLehmannRepresentation, gl::Array{T,N}, dims=1) where {T,N} + # Check dimensions + size(gl, dims) == length(dlr.basis) || + throw(DimensionMismatch("Input array has wrong size along dimension $dims")) + + # Prepare output dimensions + output_dims = collect(size(gl)) + output_dims[dims] = length(dlr) + + # Determine output type + output_type = T + output = Array{output_type,N}(undef, output_dims...) + + # Call appropriate C function + ndim = N + input_dims = Int32[size(gl)...] + target_dim = Int32(dims - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if T <: Real + ret = C_API.spir_ir2dlr_dd(dlr.ptr, order, ndim, input_dims, target_dim, gl, output) + elseif T <: Complex + ret = C_API.spir_ir2dlr_zz(dlr.ptr, order, ndim, input_dims, target_dim, gl, output) + else + error("Unsupported type: $T") end -end -function TauPoles(stats::Statistics, β::Real, poles::Vector{<:Real}) - TauPoles{typeof(stats)}(β, poles) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to transform IR to DLR: status=$ret") + return output end -function (tp::TauPoles)(τ::Vector{<:Real}) - for τ in τ - 0 ≤ τ ≤ tp.β || - throw(DomainError(τ, "τ must be in [0, β], found $τ outside of [0, $(tp.β)]]")) - end +""" + to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::Array, dims=1) - x = reshape(2τ ./ tp.β .- 1, (1, :)) - y = tp.poles ./ tp.ωmax - Λ = tp.β * tp.ωmax +Transform from DLR coefficients to IR basis coefficients. - .-LogisticKernel(Λ).(x, y) -end +# Arguments + + - `dlr`: The DLR basis + - `g_dlr`: DLR coefficients + - `dims`: Dimension along which the DLR coefficients are stored + +# Returns +IR basis coefficients with the same shape as input, but with size `length(dlr.basis)` along dimension `dims`. """ - DiscreteLehmannRepresentation <: AbstractBasis +function to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::Array{T,N}, dims=1) where {T,N} + # Check dimensions + size(g_dlr, dims) == length(dlr) || + throw(DimensionMismatch("Input array has wrong size along dimension $dims")) + + # Prepare output dimensions + output_dims = collect(size(g_dlr)) + output_dims[dims] = length(dlr.basis) + + # Determine output type + output_type = T + output = Array{output_type,N}(undef, output_dims...) + + # Call appropriate C function + ndim = N + input_dims = Int32[size(g_dlr)...] + target_dim = Int32(dims - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if T <: Real + ret = C_API.spir_dlr2ir_dd( + dlr.ptr, order, ndim, input_dims, target_dim, g_dlr, output) + elseif T <: Complex + ret = C_API.spir_dlr2ir_zz( + dlr.ptr, order, ndim, input_dims, target_dim, g_dlr, output) + else + error("Unsupported type: $T") + end -Discrete Lehmann representation (DLR) with poles selected according to extrema of IR. + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to transform DLR to IR: status=$ret") + return output +end -This class implements a variant of the discrete Lehmann representation (`DLR`) [1](https://doi.org/10.48550/arXiv.2110.06765). Instead -of a truncated singular value expansion of the analytic continuation kernel ``K`` like the IR, -the discrete Lehmann representation is based on a "sketching" of ``K``. The resulting basis -is a linear combination of discrete set of poles on the real-frequency axis, continued to the -imaginary-frequency axis: +# Pole access functions - G(iv) == sum(a[i] / (iv - w[i]) for i in range(L)) +""" + npoles(dlr::DiscreteLehmannRepresentation) -Warning -The poles on the real-frequency axis selected for the DLR are based on a rank-revealing -decomposition, which offers accuracy guarantees. Here, we instead select the pole locations -based on the zeros of the IR basis functions on the real axis, which is a heuristic. We do not -expect that difference to matter, but please don't blame the DLR authors if we were wrong :-) +Get the number of poles in the DLR basis. """ -struct DiscreteLehmannRepresentation{S<:Statistics,B<:AbstractBasis{S},T<:AbstractFloat, - FMAT<:SVD} <: AbstractBasis{S} - basis :: B - poles :: Vector{T} - u :: TauPoles{S} - uhat :: MatsubaraPoles{S} - fitmat :: Matrix{Float64} - matrix :: FMAT +function npoles(dlr::DiscreteLehmannRepresentation) + n_poles = Ref{Int32}(-1) + ret = C_API.spir_dlr_get_npoles(dlr.ptr, n_poles) + ret == C_API.SPIR_COMPUTATION_SUCCESS || error("Failed to get number of poles") + return Int(n_poles[]) end -function DiscreteLehmannRepresentation(b::AbstractBasis, - poles=default_omega_sampling_points(b)) - u = TauPoles(statistics(b), β(b), poles) - uhat = MatsubaraPoles(statistics(b), β(b), poles) +""" + get_poles(dlr::DiscreteLehmannRepresentation) - # Fitting matrix from IR - fitmat = -b.s .* b.v(poles) +Get the pole locations for the DLR basis. - # Now, here we *know* that fitmat is ill-conditioned in very particular way: - # it is a product A * B * C, where B is well conditioned and A, C are scalings. - DiscreteLehmannRepresentation(b, poles, u, uhat, fitmat, svd(fitmat; alg=QRIteration())) +Returns a vector of pole locations on the real-frequency axis. +""" +function get_poles(dlr::DiscreteLehmannRepresentation) + n = npoles(dlr) + poles = Vector{Float64}(undef, n) + ret = C_API.spir_dlr_get_poles(dlr.ptr, poles) + ret == C_API.SPIR_COMPUTATION_SUCCESS || error("Failed to get poles") + return poles end -function Base.show(io::IO, ::MIME"text/plain", dlr::DiscreteLehmannRepresentation) - print(io, "DiscreteLehmannRepresentation for $(dlr.basis) with poles at $(dlr.poles)") +# Convenience function for getting default omega sampling points +""" + default_omega_sampling_points(basis::AbstractBasis) + +Get the default real-frequency sampling points for a basis. + +These are the extrema of the highest-order basis function on the real-frequency axis, +which provide near-optimal conditioning for the DLR. +""" +function default_omega_sampling_points(basis::AbstractBasis) + n_points = Ref{Int32}(-1) + ret = C_API.spir_basis_get_n_default_ws(basis.ptr, n_points) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to get number of default omega points") + + points = Vector{Float64}(undef, n_points[]) + ret = C_API.spir_basis_get_default_ws(basis.ptr, points) + ret == C_API.SPIR_COMPUTATION_SUCCESS || error("Failed to get default omega points") + + return points end +# AbstractBasis interface implementation + Base.length(dlr::DiscreteLehmannRepresentation) = length(dlr.poles) Base.size(dlr::DiscreteLehmannRepresentation) = (length(dlr),) +# Pass through to underlying basis β(dlr::DiscreteLehmannRepresentation) = β(dlr.basis) ωmax(dlr::DiscreteLehmannRepresentation) = ωmax(dlr.basis) Λ(dlr::DiscreteLehmannRepresentation) = Λ(dlr.basis) +accuracy(dlr::DiscreteLehmannRepresentation) = accuracy(dlr.basis) +# DLR-specific methods sampling_points(dlr::DiscreteLehmannRepresentation) = dlr.poles significance(dlr::DiscreteLehmannRepresentation) = ones(size(dlr)) -accuracy(dlr::DiscreteLehmannRepresentation) = accuracy(dlr.basis) function default_tau_sampling_points(dlr::DiscreteLehmannRepresentation) default_tau_sampling_points(dlr.basis) end + function default_matsubara_sampling_points(dlr::DiscreteLehmannRepresentation; kwargs...) default_matsubara_sampling_points(dlr.basis; kwargs...) end -iswellconditioned(::DiscreteLehmannRepresentation) = false -""" - from_IR(dlr::DiscreteLehmannRepresentation, gl::AbstractArray, dims=1) - -From IR to DLR. `gl``: Expansion coefficients in IR. -""" -function from_IR(dlr::DiscreteLehmannRepresentation, gl::AbstractArray, dims=1) - mapslices(sl -> dlr.matrix \ sl, gl; dims) -end - -""" - to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::AbstractArray, dims=1) +# DLR is not as well-conditioned as IR +iswellconditioned(::DiscreteLehmannRepresentation) = false -From DLR to IR. `g_dlr``: Expansion coefficients in DLR. -""" -function to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::AbstractArray, dims=1) - mapslices(sl -> dlr.fitmat * sl, g_dlr; dims) -end +# Accessor for the underlying basis +basis(dlr::DiscreteLehmannRepresentation) = dlr.basis diff --git a/src/freq.jl b/src/freq.jl index e424f70..f2a2fa5 100644 --- a/src/freq.jl +++ b/src/freq.jl @@ -15,8 +15,6 @@ function Statistics(zeta::Integer) end end -Base.broadcastable(s::Statistics) = Ref(s) - """ Fermionic statistics. """ @@ -27,16 +25,10 @@ Bosonic statistics. """ struct Bosonic <: Statistics end -zeta(::Fermionic) = 1 -zeta(::Bosonic) = 0 - -allowed(::Type{Fermionic}, a::Integer) = isodd(a) -allowed(::Type{Bosonic}, a::Integer) = iseven(a) - -Base.:+(::Fermionic, ::Bosonic) = Fermionic() -Base.:+(::Bosonic, ::Fermionic) = Fermionic() -Base.:+(::Fermionic, ::Fermionic) = Bosonic() -Base.:+(::Bosonic, ::Bosonic) = Bosonic() +# Convert Julia statistics to C API constants +_statistics_to_c(::Type{Fermionic}) = SPIR_STATISTICS_FERMIONIC +_statistics_to_c(::Type{Bosonic}) = SPIR_STATISTICS_BOSONIC +_statistics_from_c(s::Cint) = s == SPIR_STATISTICS_FERMIONIC ? Fermionic() : Bosonic() """ MatsubaraFreq(n) @@ -79,6 +71,18 @@ const FermionicFreq = MatsubaraFreq{Fermionic} MatsubaraFreq(n::Integer) = MatsubaraFreq(Statistics(mod(n, 2)), n) +Base.broadcastable(s::Statistics) = Ref(s) +zeta(::Fermionic) = 1 +zeta(::Bosonic) = 0 + +allowed(::Type{Fermionic}, a::Integer) = isodd(a) +allowed(::Type{Bosonic}, a::Integer) = iseven(a) + +Base.:+(::Fermionic, ::Bosonic) = Fermionic() +Base.:+(::Bosonic, ::Fermionic) = Fermionic() +Base.:+(::Fermionic, ::Fermionic) = Bosonic() +Base.:+(::Bosonic, ::Bosonic) = Bosonic() + statistics(::MatsubaraFreq{S}) where {S} = S() """ diff --git a/src/gauss.jl b/src/gauss.jl deleted file mode 100644 index f794c3e..0000000 --- a/src/gauss.jl +++ /dev/null @@ -1,112 +0,0 @@ -@doc raw""" - Rule{T<:AbstractFloat} - -Quadrature rule. - -Approximation of an integral over `[a, b]` by a sum over discrete points `x` with weights `w`: -```math - ∫ f(x) ω(x) dx ≈ ∑_i f(x_i) w_i -``` -where we generally have superexponential convergence for smooth ``f(x)`` in -the number of quadrature points. -""" -struct Rule{T<:AbstractFloat} - x :: Vector{T} - w :: Vector{T} - a :: T - b :: T - x_forward :: Vector{T} - x_backward :: Vector{T} - - function Rule(x::Vector{T}, w::Vector{T}, a=(-one(T)), b=one(T), - x_forward=x .- a, x_backward=b .- x) where {T} - a ≤ b || error("a must be ≤ b") - for xx in x - a ≤ xx ≤ b || error("all x must be in [a, b], found $xx outside of [$a, $b]") - end - issorted(x) || error("x must be strictly increasing") - length(x) == length(w) || - throw(DimensionMismatch("x and w must have the same length")) - return new{T}(x, w, a, b, x_forward, x_backward) - end -end - -""" - reseat(rule, a, b) - -Reseat quadrature rule to new domain. -""" -function reseat(rule::Rule, a, b) - scaling = (b - a) / (rule.b - rule.a) - x = @. (rule.x - (rule.a + rule.b) / 2) * scaling + (a + b) / 2 - w = rule.w * scaling - x_forward = rule.x_forward * scaling - x_backward = rule.x_backward * scaling - return Rule(x, w, a, b, x_forward, x_backward) -end - -""" - scale(rule, factor) - -Scale weights by `factor`. -""" -function scale(rule, factor) - Rule(rule.x, rule.w * factor, rule.a, rule.b, rule.x_forward, rule.x_backward) -end - -""" - piecewise(rule, edges) - -Piecewise quadrature with the same quadrature rule, but scaled. -""" -function piecewise(rule, edges::Vector) - issorted(edges) || error("edges must be monotonically increasing") - start = edges[begin:(end - 1)] - stop = edges[(begin + 1):end] - return joinrules([reseat(rule, a, b) for (a, b) in zip(start, stop)]) -end - -""" - joinrules(rules) - -Join multiple Gauss quadratures together. -""" -function joinrules(rules::AbstractVector{Rule{T}}) where {T} - @inbounds for i in Iterators.drop(eachindex(rules), 1) - rules[i - 1].b == rules[i].a || error("rules must be contiguous") - end - - x = reduce(vcat, rule.x for rule in rules; init=T[]) - w = reduce(vcat, rule.w for rule in rules; init=T[]) - a = first(rules).a - b = last(rules).b - - x_forward = reduce(vcat, rule.x_forward .+ (rule.a - a) for rule in rules; init=T[]) - x_backward = reduce(vcat, rule.x_backward .+ (b - rule.b) for rule in rules; init=T[]) - - return Rule(x, w, a, b, x_forward, x_backward) -end - -""" - legendre(n[, T]) - -Gauss-Legendre quadrature with `n` points on [-1, 1]. -""" -legendre(n, (::Type{T})=Float64) where {T} = Rule(gauss(T, n)...) - -""" - legendre_collocation(rule, n=length(rule.x)) - -Generate collocation matrix from Gauss-Legendre rule. -""" -function legendre_collocation(rule, n=length(rule.x)) - res = permutedims(legvander(rule.x, n - 1) .* rule.w) - invnorm = range(0.5; length=n) - res .*= invnorm - return res -end - -function Base.convert(::Type{Rule{T}}, rule::Rule) where {T} - Rule(T.(rule.x), T.(rule.w), T(rule.a), T(rule.b), - T.(rule.x_forward), T.(rule.x_backward)) -end diff --git a/src/kernel.jl b/src/kernel.jl index 42e5f0d..9e31e28 100644 --- a/src/kernel.jl +++ b/src/kernel.jl @@ -23,16 +23,21 @@ where the weight function is given by w(y) = \frac{1}{\tanh(Λ y/2)}. ``` """ -struct LogisticKernel <: AbstractKernel +mutable struct LogisticKernel <: AbstractKernel + ptr::Ptr{spir_kernel} Λ::Float64 - function LogisticKernel(Λ) + + function LogisticKernel(Λ::Real) Λ ≥ 0 || throw(DomainError(Λ, "Kernel cutoff Λ must be non-negative")) - return new(Λ) + status = Ref{Cint}(-100) + ptr = spir_logistic_kernel_new(Float64(Λ), status) + status[] == 0 || error("Failed to create logistic kernel") + kernel = new(ptr, Float64(Λ)) + finalizer(k -> spir_kernel_release(k.ptr), kernel) + return kernel end end -Λ(kernel::LogisticKernel) = kernel.Λ - @doc raw""" RegularizedBoseKernel <: AbstractKernel @@ -45,450 +50,20 @@ integral kernel is a function on ``[-1, 1] × [-1, 1]``: ``` Care has to be taken in evaluating this expression around ``y = 0``. """ -struct RegularizedBoseKernel <: AbstractKernel +mutable struct RegularizedBoseKernel <: AbstractKernel + ptr::Ptr{spir_kernel} Λ::Float64 - function RegularizedBoseKernel(Λ) + + function RegularizedBoseKernel(Λ::Real) Λ ≥ 0 || throw(DomainError(Λ, "Kernel cutoff Λ must be non-negative")) - return new(Λ) + status = Ref{Cint}(-100) + ptr = spir_reg_bose_kernel_new(Float64(Λ), status) + status[] != 0 && error("Failed to create regularized Bose kernel") + kernel = new(ptr, Float64(Λ)) + finalizer(k -> spir_kernel_release(k.ptr), kernel) + return kernel end end +Λ(kernel::LogisticKernel) = kernel.Λ Λ(kernel::RegularizedBoseKernel) = kernel.Λ - -struct SVEHintsLogistic{T} <: AbstractSVEHints - kernel::LogisticKernel - ε::T -end - -struct SVEHintsRegularizedBose{T} <: AbstractSVEHints - kernel::RegularizedBoseKernel - ε::T -end - -struct SVEHintsReduced{T<:AbstractSVEHints} <: AbstractSVEHints - inner_hints::T -end - -@doc raw""" - ReducedKernel - -Restriction of centrosymmetric kernel to positive interval. - -For a kernel ``K`` on ``[-1, 1] × [-1, 1]`` that is centrosymmetric, i.e. -``K(x, y) = K(-x, -y)``, it is straight-forward to show that the left/right -singular vectors can be chosen as either odd or even functions. - -Consequentially, they are singular functions of a reduced kernel ``K_\mathrm{red}`` -on ``[0, 1] × [0, 1]`` that is given as either: -```math - K_\mathrm{red}(x, y) = K(x, y) \pm K(x, -y) -``` -This kernel is what this type represents. The full singular functions can be -reconstructed by (anti-)symmetrically continuing them to the negative axis. -""" -struct ReducedKernel{K<:AbstractKernel} <: AbstractReducedKernel - inner :: K - sign :: Int -end - -@doc raw""" - LogisticKernelOdd <: AbstractReducedKernel - -Fermionic analytical continuation kernel, odd. - -In dimensionless variables ``x = 2τ/β - 1``, ``y = βω/Λ``, the fermionic -integral kernel is a function on ``[-1, 1] × [-1, 1]``: -```math - K(x, y) = -\frac{\sinh(Λ x y / 2)}{\cosh(Λ y / 2)} -``` -""" -struct LogisticKernelOdd <: AbstractReducedKernel - inner :: LogisticKernel - sign :: Int - - function LogisticKernelOdd(inner::LogisticKernel, sign) - iscentrosymmetric(inner) || error("inner kernel must be centrosymmetric") - abs(sign) == 1 || throw(DomainError(sign, "sign must be -1 or 1")) - return new(inner, sign) - end -end - -@doc raw""" - RegularizedBoseKernelOdd <: AbstractReducedKernel - -Bosonic analytical continuation kernel, odd. - -In dimensionless variables ``x = 2 τ / β - 1``, ``y = β ω / Λ``, the fermionic -integral kernel is a function on ``[-1, 1] × [-1, 1]``: -```math - K(x, y) = -y \frac{\sinh(Λ x y / 2)}{\sinh(Λ y / 2)} -``` -""" -struct RegularizedBoseKernelOdd <: AbstractReducedKernel - inner :: RegularizedBoseKernel - sign :: Int - - function RegularizedBoseKernelOdd(inner::RegularizedBoseKernel, sign) - iscentrosymmetric(inner) || error("inner kernel must be centrosymmetric") - isone(abs(sign)) || throw(DomainError(sign, "sign must be -1 or 1")) - return new(inner, sign) - end -end - -@doc raw""" - xrange(kernel) - -Return a tuple ``(x_\mathrm{min}, x_\mathrm{max})`` delimiting the range of allowed `x` -values. -""" -function xrange end -xrange(::AbstractKernel) = (-1, 1) -xrange(kernel::AbstractReducedKernel) = (0, last(xrange(kernel.inner))) - -@doc raw""" - yrange(kernel) - -Return a tuple ``(y_\mathrm{min}, y_\mathrm{max})`` delimiting the range of allowed `y` -values. -""" -function yrange end -yrange(::AbstractKernel) = (-1, 1) -yrange(kernel::AbstractReducedKernel) = (0, last(yrange(kernel.inner))) - -function compute(::LogisticKernel, u₊, u₋, v) - # By introducing u_± = (1 ± x)/2 and v = Λ * y, we can write - # the kernel in the following two ways: - # - # k = exp(-u₊ * v) / (exp(-v) + 1) - # = exp(-u₋ * -v) / (exp(v) + 1) - # - # We need to use the upper equation for v ≥ 0 and the lower one for - # v < 0 to avoid overflowing both numerator and denominator - - mabsv = -abs(v) - enum = exp(mabsv * ifelse(v ≥ 0, u₊, u₋)) - denom = 1 + exp(mabsv) - return enum / denom -end - -function compute(kernel::RegularizedBoseKernel, u₊, u₋, v::T) where {T} - # With "reduced variables" u, v we have: - # - # K = -1/Λ * exp(-u_+ * v) * v / (exp(-v) - 1) - # = -1/Λ * exp(-u_- * -v) * (-v) / (exp(v) - 1) - # - # where we again need to use the upper equation for v ≥ 0 and the - # lower one for v < 0 to avoid overflow. - absv = abs(v) - enum = exp(-absv * (v ≥ 0 ? u₊ : u₋)) - - # The expression ``v / (exp(v) - 1)`` is tricky to evaluate: firstly, - # it has a singularity at v=0, which can be cured by treating that case - # separately. Secondly, the denominator loses precision around 0 since - # exp(v) = 1 + v + ..., which can be avoided using expm1(...) - denom = absv ≥ 1e-200 ? absv / expm1(-absv) : -one(absv) - return -1 / T(kernel.Λ) * enum * denom -end - -""" - segments_x(sve_hints::AbstractSVEHints[, T]) - -Segments for piecewise polynomials on the ``x`` axis. - -List of segments on the ``x`` axis for the associated piecewise polynomial. Should reflect -the approximate position of roots of a high-order singular function in ``x``. -""" -function segments_x(hints::SVEHintsLogistic, (::Type{T})=Float64) where {T} - nzeros = max(round(Int, 15 * log10(hints.kernel.Λ)), 1) - temp = T(0.143) * range(0; length=nzeros) - diffs = @. inv(cosh(temp)) - zeros = cumsum(diffs) - zeros ./= last(zeros) - return T[-reverse(zeros); zero(T); zeros] -end - -""" - segments_y(sve_hints::AbstractSVEHints[, T]) - -Segments for piecewise polynomials on the ``y`` axis. - -List of segments on the ``y`` axis for the associated piecewise polynomial. Should reflect -the approximate position of roots of a high-order singular function in ``y``. -""" -function segments_y(hints::SVEHintsLogistic, (::Type{T})=Float64) where {T} - nzeros = max(round(Int, 20 * log10(hints.kernel.Λ)), 2) - - # Zeros around -1 and 1 are distributed asymptotically identically - diffs = T[0.01523, 0.03314, 0.04848, 0.05987, 0.06703, 0.07028, 0.07030, - 0.06791, 0.06391, 0.05896, 0.05358, 0.04814, 0.04288, 0.03795, - 0.03342, 0.02932, 0.02565, 0.02239, 0.01951, 0.01699][1:min( - nzeros, 20)] - - temp = T(0.141) * (20:(nzeros - 1)) - trailing_diffs = [0.25 * exp(-x) for x in temp] - append!(diffs, trailing_diffs) - zeros = cumsum(diffs) - zeros ./= pop!(zeros) - zeros .-= one(T) - return T[-one(T); zeros; zero(T); -reverse(zeros); one(T)] -end - -function segments_x(hints::SVEHintsRegularizedBose, (::Type{T})=Float64) where {T} - # Somewhat less accurate... - nzeros = max(round(Int, 15 * log10(hints.kernel.Λ)), 15) - temp = T(0.18) * range(0; length=nzeros) - diffs = @. inv(cosh(temp)) - zeros = cumsum(diffs) - zeros ./= last(zeros) - return T[-reverse(zeros); zero(T); zeros] -end - -function segments_y(hints::SVEHintsRegularizedBose, (::Type{T})=Float64) where {T} - nzeros = max(round(Int, 20 * log10(hints.kernel.Λ)), 20) - i = range(0; length=nzeros) - diffs = @. T(0.12 / exp(0.0337 * i * log(i + 1))) - zeros = cumsum(diffs) - zeros ./= pop!(zeros) - zeros .-= one(T) - return T[-one(T); zeros; zero(T); -reverse(zeros); one(T)] -end - -""" - matrix_from_gauss(kernel, gauss_x, gauss_y) - -Compute matrix for kernel from Gauss rules. -""" -function matrix_from_gauss(kernel, gauss_x::Rule{T}, gauss_y::Rule{T})::Matrix{T} where {T} - # (1 ± x) is problematic around x = -1 and x = 1, where the quadrature - # nodes are clustered most tightly. Thus we have the need for the - # matrix method. - n = length(gauss_x.x) - m = length(gauss_y.x) - res = Matrix{T}(undef, n, m) - Threads.@threads for i in eachindex(gauss_x.x) - @inbounds @simd for j in eachindex(gauss_y.x) - res[i, j] = kernel(gauss_x.x[i], gauss_y.x[j], - gauss_x.x_forward[i], gauss_x.x_backward[i]) - end - end - res -end - -function Base.checkbounds(::Type{Bool}, kernel::AbstractKernel, x::Real, y::Real) - xmin, xmax = xrange(kernel) - ymin, ymax = yrange(kernel) - (xmin ≤ x ≤ xmax) && (ymin ≤ y ≤ ymax) -end - -function Base.checkbounds(kernel::AbstractKernel, x::Real, y::Real) - checkbounds(Bool, kernel, x, y) || throw(BoundsError(kernel, (x, y))) -end - -function compute_uv(Λ, x, y, x₊=1 + x, x₋=1 - x) - u₊ = x₊ / 2 - u₋ = x₋ / 2 - v = Λ * y - return u₊, u₋, v -end - -""" - get_symmetrized(kernel, sign) - -Construct a symmetrized version of `kernel`, i.e. `kernel(x, y) + sign * kernel(x, -y)`. - -!!! warning "Beware!" - - By default, this returns a simple wrapper over the current instance which naively - performs the sum. You may want to override this to avoid cancellation. -""" -get_symmetrized(kernel::AbstractKernel, sign) = ReducedKernel(kernel, sign) - -function get_symmetrized(kernel::LogisticKernel, sign) - sign == -1 && return LogisticKernelOdd(kernel, sign) - return Base.invoke(get_symmetrized, Tuple{AbstractKernel,typeof(sign)}, kernel, sign) -end - -function get_symmetrized(kernel::RegularizedBoseKernel, sign) - sign == -1 && return RegularizedBoseKernelOdd(kernel, sign) - return Base.invoke(get_symmetrized, Tuple{AbstractKernel,typeof(sign)}, kernel, sign) -end - -get_symmetrized(::AbstractReducedKernel, sign) = error("cannot symmetrize twice") - -function callreduced(kernel::AbstractReducedKernel, x, y, x₊, x₋) - @boundscheck checkbounds(kernel, x, y) - - # The reduced kernel is defined only over the interval [0, 1], which - # means we must add one to get the x_plus for the inner kernels. We - # can compute this as 1 + x, since we are away from -1. - x₊ = 1 + x₊ - - K₊ = kernel.inner(x, +y, x₊, x₋) - K₋ = kernel.inner(x, -y, x₊, x₋) - return K₊ + kernel.sign * K₋ -end - -(kernel::ReducedKernel)(x, y, x₊, x₋) = callreduced(kernel, x, y, x₊, x₋) - -""" - iscentrosymmetric(kernel) - -Return `true` if `kernel(x, y) == kernel(-x, -y)` for all values of `x` and `y` -in range. This allows the kernel to be block-diagonalized, speeding up the singular -value expansion by a factor of 4. Defaults to `false`. -""" -function iscentrosymmetric end -iscentrosymmetric(::LogisticKernel) = true -iscentrosymmetric(::RegularizedBoseKernel) = true -iscentrosymmetric(::AbstractReducedKernel) = false - -""" - (kernel::AbstractKernel)(x, y[, x₊, x₋]) - -Evaluate `kernel` at point `(x, y)`. - -The parameters `x₊` and `x₋`, if given, shall contain the values of `x - xₘᵢₙ` and -`xₘₐₓ - x`, respectively. This is useful if either difference is to be formed and -cancellation expected. -""" -function (kernel::AbstractKernel)(x, y, - x₊=x - first(xrange(kernel)), - x₋=last(xrange(kernel)) - x) - @boundscheck checkbounds(kernel, x, y) - u₊, u₋, v = compute_uv(kernel.Λ, x, y, x₊, x₋) - return compute(kernel, u₊, u₋, v) -end - -function (kernel::LogisticKernelOdd)(x, y, - x₊=x - first(xrange(kernel)), - x₋=last(xrange(kernel)) - x) - # For x * y around 0, antisymmetrization introduces cancellation, which - # reduces the relative precision. To combat this, we replace the - # values with the explicit form - v_half = kernel.inner.Λ / 2 * y - xy_small = x * v_half < 1 - cosh_finite = v_half < 85 - if xy_small && cosh_finite - return -sinh(v_half * x) / cosh(v_half) - else - return callreduced(kernel, x, y, x₊, x₋) - end -end - -function (kernel::RegularizedBoseKernelOdd)(x, y, - x₊=x - first(xrange(kernel)), - x₋=last(xrange(kernel)) - x) - # For x * y around 0, antisymmetrization introduces cancellation, which - # reduces the relative precision. To combat this, we replace the - # values with the explicit form. - v_half = kernel.inner.Λ / 2 * y - xv_half = x * v_half - xy_small = xv_half < 1 - sinh_range = 1e-200 < v_half < 85 - if xy_small && sinh_range - return -y * sinh(xv_half) / sinh(v_half) - else - return callreduced(kernel, x, y, x₊, x₋) - end -end - -function segments_x(hints::SVEHintsReduced, (::Type{T})=Float64) where {T} - symm_segments(segments_x(hints.inner_hints, T)) -end -function segments_y(hints::SVEHintsReduced, (::Type{T})=Float64) where {T} - symm_segments(segments_y(hints.inner_hints, T)) -end - -function symm_segments(x::AbstractVector{T}) where {T} - for (xi, revxi) in zip(x, Iterators.reverse(x)) - xi ≈ -revxi || error("segments must be symmetric") - end - xpos = x[(begin + length(x) ÷ 2):end] - iszero(first(xpos)) || pushfirst!(xpos, zero(T)) - return xpos -end - -""" - sve_hints(kernel, ε) - -Provide discretisation hints for the SVE routines. - -Advises the SVE routines of discretisation parameters suitable in -transforming the (infinite) SVE into an (finite) SVD problem. - -See also [`AbstractSVEHints`](@ref). -""" -function sve_hints end -sve_hints(kernel::LogisticKernel, ε) = SVEHintsLogistic(kernel, ε) -sve_hints(kernel::RegularizedBoseKernel, ε) = SVEHintsRegularizedBose(kernel, ε) -sve_hints(kernel::AbstractReducedKernel, ε) = SVEHintsReduced(sve_hints(kernel.inner, ε)) - -""" - nsvals(hints) - -Upper bound for number of singular values. - -Upper bound on the number of singular values above the given threshold, i.e. where -`s[l] ≥ ε * first(s)`. -""" -function nsvals(hints::SVEHintsLogistic) - log10_Λ = max(1, log10(hints.kernel.Λ)) - return round(Int, (25 + log10_Λ) * log10_Λ) -end -function nsvals(hints::SVEHintsRegularizedBose) - log10_Λ = max(1, log10(hints.kernel.Λ)) - return round(Int, 28 * log10_Λ) -end -function nsvals(hints::SVEHintsReduced) - return (nsvals(hints.inner_hints) + 1) ÷ 2 -end - -""" - ngauss(hints) - -Gauss-Legendre order to use to guarantee accuracy. -""" -function ngauss end -ngauss(hints::SVEHintsLogistic) = hints.ε ≥ sqrt(eps()) ? 10 : 16 -ngauss(hints::SVEHintsRegularizedBose) = hints.ε ≥ sqrt(eps()) ? 10 : 16 -ngauss(hints::SVEHintsReduced) = ngauss(hints.inner_hints) - -""" - ypower(kernel) - -Power with which the ``y`` coordinate scales. -""" -function ypower end -ypower(::AbstractKernel) = 0 -ypower(::RegularizedBoseKernel) = 1 -ypower(kernel::AbstractReducedKernel) = ypower(kernel.inner) - -""" - conv_radius(kernel) - -Convergence radius of the Matsubara basis asymptotic model. - -For improved relative numerical accuracy, the IR basis functions on the -Matsubara axis `uhat(basis, n)` can be evaluated from an asymptotic -expression for `abs(n) > conv_radius`. If `isinf(conv_radius)`, then -the asymptotics are unused (the default). -""" -function conv_radius end -conv_radius(kernel::LogisticKernel) = 40 * kernel.Λ -conv_radius(kernel::RegularizedBoseKernel) = 40 * kernel.Λ -conv_radius(kernel::AbstractReducedKernel) = conv_radius(kernel.inner) - -""" - weight_func(kernel, statistics::Statistics) - -Return the weight function for the given statistics. - - - Fermion: `w(x) == 1` - - Boson: `w(y) == 1/tanh(Λ*y/2)` -""" -function weight_func end -weight_func(::AbstractKernel, ::Statistics) = one -weight_func(::LogisticKernel, ::Fermionic) = one -weight_func(kernel::LogisticKernel, ::Bosonic) = y -> 1 / tanh(0.5 * kernel.Λ * y) -weight_func(::RegularizedBoseKernel, ::Fermionic) = error("Kernel is designed for bosonic functions") -weight_func(::RegularizedBoseKernel, ::Bosonic) = inv diff --git a/src/poly.jl b/src/poly.jl index bd50af4..38214df 100644 --- a/src/poly.jl +++ b/src/poly.jl @@ -7,533 +7,204 @@ Models a function on the interval ``[xmin, xmax]`` as a set of segments on the intervals ``S[i] = [a[i], a[i+1]]``, where on each interval the function is expanded in scaled Legendre polynomials. """ -struct PiecewiseLegendrePoly <: Function - polyorder :: Int - xmin :: Float64 - xmax :: Float64 - - knots :: Vector{Float64} - Δx :: Vector{Float64} - data :: Matrix{Float64} - symm :: Int - l :: Int - - xm :: Vector{Float64} - inv_xs :: Vector{Float64} - norms :: Vector{Float64} - - function PiecewiseLegendrePoly(polyorder::Integer, xmin::Real, - xmax::Real, knots::AbstractVector, - Δx::AbstractVector, data::AbstractMatrix, symm::Integer, - l::Integer, xm::AbstractVector, inv_xs::AbstractVector, - norms::AbstractVector) - !any(isnan, data) || error("data contains NaN") - issorted(knots) || error("knots must be monotonically increasing") - @inbounds for i in eachindex(Δx) - Δx[i] ≈ knots[i + 1] - knots[i] || error("Δx must work with knots") - end - return new(polyorder, xmin, xmax, knots, Δx, data, symm, l, xm, inv_xs, norms) +mutable struct PiecewiseLegendrePoly + ptr::Ptr{spir_funcs} + xmin::Float64 + xmax::Float64 + function PiecewiseLegendrePoly(funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64) + result = new(funcs, xmin, xmax) + finalizer(r -> spir_funcs_release(r.ptr), result) + return result end end -function PiecewiseLegendrePoly(data, p::PiecewiseLegendrePoly; symm=symm(p)) - return PiecewiseLegendrePoly( - polyorder(p), xmin(p), xmax(p), copy(knots(p)), copy(Δx(p)), - data, symm, p.l, copy(p.xm), copy(p.inv_xs), copy(norms(p))) -end - -function PiecewiseLegendrePoly(data::Matrix, knots::Vector, l::Integer; - Δx=diff(knots), symm=0) - polyorder, nsegments = size(data) - length(knots) == nsegments + 1 || error("Invalid knots array") - xm = @views @. (knots[begin:(end - 1)] + knots[(begin + 1):end]) / 2 - inv_xs = 2 ./ Δx - norms = sqrt.(inv_xs) - return PiecewiseLegendrePoly(polyorder, first(knots), last(knots), knots, - Δx, data, symm, l, xm, inv_xs, norms) -end - -Base.size(::PiecewiseLegendrePoly) = () - -xmin(p::PiecewiseLegendrePoly) = p.xmin -xmax(p::PiecewiseLegendrePoly) = p.xmax -knots(p::PiecewiseLegendrePoly) = p.knots -Δx(p::PiecewiseLegendrePoly) = p.Δx -symm(p::PiecewiseLegendrePoly) = p.symm -data(p::PiecewiseLegendrePoly) = p.data -norms(p::PiecewiseLegendrePoly) = p.norms -polyorder(p::PiecewiseLegendrePoly) = p.polyorder - -function Base.show(io::IO, ::MIME"text/plain", p::PiecewiseLegendrePoly) - print(io, "PiecewiseLegendrePoly on [$(xmin(p)), $(xmax(p))], order=$(polyorder(p))") -end - -@inline function (poly::PiecewiseLegendrePoly)(x::Real) - i, x̃ = split(poly, x) - return @inbounds legval(x̃, @view data(poly)[:, i]) * norms(poly)[i] -end -(poly::PiecewiseLegendrePoly)(xs::AbstractVector) = poly.(xs) - -""" - overlap(poly::PiecewiseLegendrePoly, f; - rtol=eps(T), return_error=false, maxevals=10^4, points=T[]) - -Evaluate overlap integral of `poly` with arbitrary function `f`. - -Given the function `f`, evaluate the integral - - ∫ dx f(x) poly(x) - -using adaptive Gauss-Legendre quadrature. - -`points` is a sequence of break points in the integration interval where local -difficulties of the integrand may occur (e.g. singularities, discontinuities). -""" -function overlap(poly::PiecewiseLegendrePoly, f::F; - rtol=eps(), return_error=false, maxevals=10^4, points=Float64[]) where {F} - int_result, - int_error = quadgk(x -> poly(x) * f(x), - unique!(sort!([knots(poly); points]))...; - rtol, order=10, maxevals) - if return_error - return int_result, int_error - else - return int_result - end -end - -""" - deriv(poly[, ::Val{n}=Val(1)]) - -Get polynomial for the `n`th derivative. -""" -function deriv(poly::PiecewiseLegendrePoly, (::Val{n})=Val(1)) where {n} - ddata = legder(poly.data, n) - - @views @inbounds for i in axes(ddata, 2) - ddata[:, i] .*= poly.inv_xs[i]^n - end - return PiecewiseLegendrePoly(ddata, poly; symm=(-1)^n * symm(poly)) -end - -""" - roots(poly) - -Find all roots of the piecewise polynomial `poly`. -""" -function roots(poly::PiecewiseLegendrePoly; tol=1e-10, alpha=Val(2)) - grid = knots(poly) - grid = refine_grid(grid, alpha) - return find_all(poly, grid) -end - -function Base.checkbounds(::Type{Bool}, poly::PiecewiseLegendrePoly, x::Real) - xmin(poly) ≤ x ≤ xmax(poly) -end - -function Base.checkbounds(poly::PiecewiseLegendrePoly, x::Real) - checkbounds(Bool, poly, x) || - throw(DomainError(x, "The domain is [$(xmin(poly)), $(xmax(poly))]")) -end - -""" - split(poly, x) - -Split segment. - -Find segment of poly's domain that covers `x`. -""" -@inline function split(poly, x::Real) - @boundscheck checkbounds(poly, x) - - i = max(searchsortedlast(knots(poly), x; lt=≤), 1) - x̃ = x - poly.xm[i] - x̃ *= poly.inv_xs[i] - return i, x̃ -end - -function Base.:*(poly::PiecewiseLegendrePoly, factor::Number) - return PiecewiseLegendrePoly(data(poly) * factor, knots(poly), poly.l; - Δx=Δx(poly), symm=symm(poly)) -end -Base.:*(factor::Number, poly::PiecewiseLegendrePoly) = poly * factor -function Base.:+(p1::PiecewiseLegendrePoly, p2::PiecewiseLegendrePoly) - knots(p1) == knots(p2) || error("knots must be the same") - return PiecewiseLegendrePoly(data(p1) + data(p2), knots(p1), -1; - Δx=Δx(p1), symm=symm(p1) == symm(p2) ? symm(p1) : 0) -end -function Base.:-(poly::PiecewiseLegendrePoly) - return PiecewiseLegendrePoly(-data(poly), knots(poly), -1; - Δx=Δx(poly), symm=symm(poly)) -end -Base.:-(p1::PiecewiseLegendrePoly, p2::PiecewiseLegendrePoly) = p1 + (-p2) - -################################# -## PiecewiseLegendrePolyVector ## -################################# - """ PiecewiseLegendrePolyVector Contains a `Vector{PiecewiseLegendrePoly}`. """ -struct PiecewiseLegendrePolyVector <: AbstractVector{PiecewiseLegendrePoly} - polyvec::Vector{PiecewiseLegendrePoly} -end - -Base.size(polys::PiecewiseLegendrePolyVector) = size(polys.polyvec) - -Base.IndexStyle(::Type{PiecewiseLegendrePolyVector}) = IndexLinear() - -Base.getindex(polys::PiecewiseLegendrePolyVector, i) = getindex(polys.polyvec, i) -function Base.getindex(polys::PiecewiseLegendrePolyVector, i::AbstractVector) - PiecewiseLegendrePolyVector(getindex(polys.polyvec, i)) -end - -Base.setindex!(polys::PiecewiseLegendrePolyVector, p, i) = setindex!(polys.polyvec, p, i) - -function Base.similar(polys::PiecewiseLegendrePolyVector) - PiecewiseLegendrePolyVector(similar(polys.polyvec)) -end - -function Base.show(io::IO, ::MIME"text/plain", polys::PiecewiseLegendrePolyVector) - print(io, "$(length(polys))-element PiecewiseLegendrePolyVector ") - print(io, "on [$(xmin(polys)), $(xmax(polys))]") -end - -function PiecewiseLegendrePolyVector(data::AbstractArray{T,3}, knots::Vector{T}; - symm=zeros(Int, size(data, 3))) where {T<:Real} - PiecewiseLegendrePolyVector(map(axes(data, 3)) do i - PiecewiseLegendrePoly(data[:, :, i], knots, i - 1; symm=symm[i]) - end) -end - -function PiecewiseLegendrePolyVector(polys::PiecewiseLegendrePolyVector, - knots::AbstractVector; Δx=diff(knots), symm=0) - length(polys) == length(symm) || - throw(DimensionMismatch("Sizes of polys and symm don't match")) - - PiecewiseLegendrePolyVector(map(zip(polys, symm)) do (poly, sym) - PiecewiseLegendrePoly(poly.data, knots, poly.l; Δx, symm=sym) - end) -end - -function PiecewiseLegendrePolyVector(data::AbstractArray{T,3}, - polys::PiecewiseLegendrePolyVector) where {T} - size(data, 3) == length(polys) || - throw(DimensionMismatch("Sizes of data and polys don't match")) - - PiecewiseLegendrePolyVector(map(eachindex(polys)) do i - PiecewiseLegendrePoly(data[:, :, i], polys[i]) - end) -end - -for name in (:xmin, :xmax, :knots, :Δx, :polyorder, :norms) - eval(:($name(polys::PiecewiseLegendrePolyVector) = $name(first(polys.polyvec)))) -end - -symm(polys::PiecewiseLegendrePolyVector) = map(symm, polys) - -function data(polys::PiecewiseLegendrePolyVector) - data = Array{Float64,3}(undef, size(first(polys).data)..., length(polys)) - @inbounds for i in eachindex(polys) - data[:, :, i] .= polys[i].data +mutable struct PiecewiseLegendrePolyVector + ptr::Ptr{spir_funcs} + xmin::Float64 + xmax::Float64 + function PiecewiseLegendrePolyVector( + funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64) + result = new(funcs, xmin, xmax) + finalizer(r -> spir_funcs_release(r.ptr), result) + return result end - return data end -(polys::PiecewiseLegendrePolyVector)(x) = [poly(x) for poly in polys] -function (polys::PiecewiseLegendrePolyVector)(x::AbstractArray) - reshape(mapreduce(polys, vcat, x; init=Float64[]), (length(polys), size(x)...)) -end - -# Backward compatibility -function overlap(polys::PiecewiseLegendrePolyVector, f::F; rtol=eps(), - return_error=false) where {F} - overlap.(polys, f; rtol, return_error) -end - -######################### -## PiecewiseLegendreFT ## -######################### - -""" - PowerModel - -Model from a high-frequency series expansion:: - - A(iω) == sum(A[n] / (iω)^(n+1) for n in 1:N) - -where ``iω == i * π/2 * wn`` is a reduced imaginary frequency, i.e., -``wn`` is an odd/even number for fermionic/bosonic frequencies. -""" -struct PowerModel{T<:AbstractFloat} - moments::Vector{T} -end - -const DEFAULT_GRID = [range(0; length=2^6); - trunc.(Int, exp2.(range(6, 25; length=32 * (25 - 6) + 1)))] - """ - PiecewiseLegendreFT <: Function + PiecewiseLegendreFTVector -Fourier transform of a piecewise Legendre polynomial. +Fourier transform of piecewise Legendre polynomials. For a given frequency index `n`, the Fourier transform of the Legendre function is defined as: p̂(n) == ∫ dx exp(im * π * n * x / (xmax - xmin)) p(x) - -The polynomial is continued either periodically (`freq=:even`), in which -case `n` must be even, or antiperiodically (`freq=:odd`), in which case -`n` must be odd. """ -struct PiecewiseLegendreFT{S<:Statistics} <: Function - poly :: PiecewiseLegendrePoly - n_asymp :: Float64 - model :: PowerModel{Float64} +mutable struct PiecewiseLegendreFTVector + ptr::Ptr{spir_funcs} + xmin::Float64 + xmax::Float64 + + function PiecewiseLegendreFTVector(funcs::Ptr{spir_funcs}) + xmin = -1.0 + xmax = 1.0 + result = new(funcs, xmin, xmax) + finalizer(r -> spir_funcs_release(r.ptr), result) + return result + end end -function PiecewiseLegendreFT(poly::PiecewiseLegendrePoly, stat::Statistics; n_asymp=Inf) - (xmin(poly), xmax(poly)) == (-1, 1) || error("Only interval [-1, 1] is supported") - model = power_model(stat, poly) - PiecewiseLegendreFT{typeof(stat)}(poly, Float64(n_asymp), model) +function (polys::PiecewiseLegendrePoly)(x::Real) + sz = Ref{Int32}(-1) + spir_funcs_get_size(polys.ptr, sz) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get funcs size") + ret = Vector{Float64}(undef, Int(sz[])) + spir_funcs_eval(polys.ptr, x, ret) == SPIR_COMPUTATION_SUCCESS || + error("Failed to evaluate funcs") + return only(ret) end -n_asymp(polyFT::PiecewiseLegendreFT) = polyFT.n_asymp -statistics(::PiecewiseLegendreFT{S}) where {S} = S() -zeta(polyFT::PiecewiseLegendreFT) = zeta(statistics(polyFT)) -poly(polyFT::PiecewiseLegendreFT) = polyFT.poly - -struct PiecewiseLegendreFTVector{S} <: AbstractVector{PiecewiseLegendreFT{S}} - polyvec::Vector{PiecewiseLegendreFT{S}} +function (polys::PiecewiseLegendrePolyVector)(x::Real) + sz = Ref{Int32}(-1) + spir_funcs_get_size(polys.ptr, sz) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get funcs size") + ret = Vector{Float64}(undef, Int(sz[])) + spir_funcs_eval(polys.ptr, x, ret) == SPIR_COMPUTATION_SUCCESS || + error("Failed to evaluate funcs") + return ret end -Base.size(polys::PiecewiseLegendreFTVector) = size(polys.polyvec) - -Base.IndexStyle(::Type{PiecewiseLegendreFTVector}) = IndexLinear() - -Base.getindex(polys::PiecewiseLegendreFTVector, i) = getindex(polys.polyvec, i) -function Base.getindex(polys::PiecewiseLegendreFTVector, i::AbstractVector) - PiecewiseLegendreFTVector(getindex(polys.polyvec, i)) +function (polys::PiecewiseLegendrePolyVector)(x::AbstractVector) + hcat(polys.(x)...) end -Base.setindex!(polys::PiecewiseLegendreFTVector, p, i) = setindex!(polys.polyvec, p, i) +function (polys::PiecewiseLegendreFTVector)(freq::MatsubaraFreq) + n = freq.n + sz = Ref{Int32}(-1) + spir_funcs_get_size(polys.ptr, sz) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get funcs size") + ret = Vector{ComplexF64}(undef, Int(sz[])) + spir_funcs_eval_matsu(polys.ptr, n, ret) == SPIR_COMPUTATION_SUCCESS || + error("Failed to evaluate funcs") + return ret +end -function Base.similar(polys::PiecewiseLegendreFTVector) - PiecewiseLegendreFTVector(similar(polys.polyvec)) +function (polys::PiecewiseLegendreFTVector)(x::AbstractVector) + hcat(polys.(x)...) end -function PiecewiseLegendreFTVector(polys::PiecewiseLegendrePolyVector, - stat::Statistics; n_asymp=Inf) - PiecewiseLegendreFTVector(map(polys) do poly - PiecewiseLegendreFT(poly, stat; n_asymp) - end) +function Base.getindex(funcs::Ptr{spir_funcs}, i::Int) + status = Ref{Int32}(-100) + indices = Vector{Int32}(undef, 1) + indices[1] = i - 1 # Julia indices are 1-based, C indices are 0-based + ret = spir_funcs_get_slice(funcs, 1, indices, status) + status[] == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis function for index $i: $(status[])") + return ret end -n_asymp(polyFTs::PiecewiseLegendreFTVector) = n_asymp(first(polyFTs)) -statistics(polyFTs::PiecewiseLegendreFTVector) = statistics(first(polyFTs)) -zeta(polyFTs::PiecewiseLegendreFTVector) = zeta(first(polyFTs)) -poly(polyFTs::PiecewiseLegendreFTVector) = PiecewiseLegendrePolyVector(map(poly, polyFTs)) +function Base.getindex(polys::PiecewiseLegendrePolyVector, i::Int) + return PiecewiseLegendrePoly(polys.ptr[i], polys.xmin, polys.xmax) +end -""" - (polyFT::PiecewiseLegendreFT)(ω) +# Base.getindex(funcs::Ptr{spir_funcs}, I) = [funcs[i] for i in I] +function Base.getindex(polys::PiecewiseLegendrePolyVector, I) + PiecewiseLegendrePoly[polys[i] + for i in I] +end -Obtain Fourier transform of polynomial for given `MatsubaraFreq` `ω`. -""" -function (polyFT::Union{PiecewiseLegendreFT{S}, - PiecewiseLegendreFTVector{S}})(ω::MatsubaraFreq{S}) where {S} - n = Int(ω) - return if abs(n) < n_asymp(polyFT) - compute_unl_inner(poly(polyFT), n) - else - giw(polyFT, n) - end +function Base.length(funcs::Ptr{spir_funcs}) + sz = Ref{Int32}(-1) + spir_funcs_get_size(funcs, sz) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get funcs size") + return Int(sz[]) end -(polyFT::PiecewiseLegendreFT)(n::Integer) = polyFT(MatsubaraFreq(n)) -(polyFT::PiecewiseLegendreFTVector)(n::Integer) = polyFT(MatsubaraFreq(n)) -(polyFT::PiecewiseLegendreFT)(n::AbstractArray) = polyFT.(n) -function (polyFTs::PiecewiseLegendreFTVector)(n::AbstractArray) - reshape(mapreduce(polyFTs, vcat, n)::Vector{ComplexF64}, (length(polyFTs), size(n)...)) +function Base.length(polys::PiecewiseLegendrePolyVector) + return length(polys.ptr) end -""" - giw(polyFT, wn) +Base.firstindex(funcs::Ptr{spir_funcs}) = 1 +Base.lastindex(funcs::Ptr{spir_funcs}) = length(funcs) -Return model Green's function for reduced frequencies -""" -function giw(polyFT, wn::Integer) - iw = im * π / 2 * wn - iszero(wn) && return zero(iw) - inv_iw = 1 / iw - return inv_iw * evalpoly(inv_iw, moments(polyFT)) -end +Base.firstindex(polys::PiecewiseLegendrePolyVector) = firstindex(polys.ptr) +Base.lastindex(polys::PiecewiseLegendrePolyVector) = lastindex(polys.ptr) -moments(polyFT::PiecewiseLegendreFT) = polyFT.model.moments -function moments(polyFTs::PiecewiseLegendreFTVector) - n = length(first(polyFTs).model.moments) - return [[p.model.moments[i] for p in polyFTs] for i in 1:n] -end +function roots(poly::PiecewiseLegendrePoly) + nroots_ref = Ref{Int32}(-1) + spir_funcs_get_n_roots(poly.ptr, nroots_ref) + nroots = nroots_ref[] -""" - find_extrema(polyFT::PiecewiseLegendreFT; part=nothing, grid=DEFAULT_GRID) + out = Vector{Float64}(undef, nroots) -Obtain extrema of Fourier-transformed polynomial. -""" -function find_extrema(û::PiecewiseLegendreFT; part=nothing, grid=DEFAULT_GRID, - positive_only=false) - f = func_for_part(û, part) - x₀ = discrete_extrema(f, grid) - x₀ .= 2x₀ .+ zeta(statistics(û)) - positive_only || symmetrize_matsubara!(x₀) - return MatsubaraFreq.(statistics(û), x₀) + SparseIR.C_API.spir_funcs_get_roots( + poly.ptr, out + ) + return out end -function sign_changes(û::PiecewiseLegendreFT; part=nothing, grid=DEFAULT_GRID, - positive_only=false) - f = func_for_part(û, part) - x₀ = find_all(f, grid) - x₀ .= 2x₀ .+ zeta(statistics(û)) - positive_only || symmetrize_matsubara!(x₀) - return MatsubaraFreq.(statistics(û), x₀) -end +function roots(poly::PiecewiseLegendrePolyVector) + nroots_ref = Ref{Int32}(-1) + spir_funcs_get_n_roots(poly.ptr, nroots_ref) + nroots = nroots_ref[] -function func_for_part(polyFT::PiecewiseLegendreFT{S}, part=nothing) where {S} - if isnothing(part) - parity = symm(poly(polyFT)) - if parity == 1 - part = statistics(polyFT) isa Bosonic ? real : imag - elseif parity == -1 - part = statistics(polyFT) isa Bosonic ? imag : real - else - error("Cannot detect parity") - end - end - let polyFT = polyFT - n -> part(polyFT(MatsubaraFreq{S}(2n + zeta(polyFT))))::Float64 - end -end + out = Vector{Float64}(undef, nroots) -function symmetrize_matsubara!(x₀) - issorted(x₀) || error("set of Matsubara points not ordered") - first(x₀) ≥ 0 || error("points must be non-negative") - revx₀ = reverse(x₀) - iszero(first(x₀)) && popfirst!(x₀) - prepend!(x₀, -revx₀) + SparseIR.C_API.spir_funcs_get_roots( + poly.ptr, out + ) + return out end -function derivs(ppoly, x) - res = [ppoly(x)] - for _ in 2:(polyorder(ppoly)) - ppoly = deriv(ppoly) - push!(res, ppoly(x)) - end - return res +function overlap(poly::PiecewiseLegendrePolyVector, f::F) where {F} + xmin = poly.xmin + xmax = poly.xmax + pts = filter(x -> xmin ≤ x ≤ xmax, roots(poly)) + q, + _ = quadgk( + x -> poly(x) * f(x), + unique!(sort!(vcat(pts, [xmin, xmax]))); + rtol=eps(), order=10, maxevals=10^4) + q end -function power_moments!(stat, deriv_x1, l) - statsign = zeta(stat) == 1 ? -1 : 1 - @inbounds for m in 1:length(deriv_x1) - deriv_x1[m] *= -(statsign * (-1)^m + (-1)^l) / sqrt(2) - end - deriv_x1 +function overlap(poly::PiecewiseLegendrePoly, f::F) where {F} + xmin = poly.xmin + xmax = poly.xmax + pts = filter(x -> xmin ≤ x ≤ xmax, roots(poly)) + q, + _ = quadgk( + x -> poly(x) * f(x), + unique!(sort!(vcat(pts, [xmin, xmax]))); + rtol=eps(), order=10, maxevals=10^4) + return q end -function power_model(stat, poly) - deriv_x1 = derivs(poly, 1.0) - moments = power_moments!(stat, deriv_x1, poly.l) - return PowerModel(moments) +function xmin(poly::PiecewiseLegendrePoly) + return poly.xmin end -######################## -### Helper Functions ### -######################## - -""" - compute_unl_inner(poly, wn) - -Compute piecewise Legendre to Matsubara transform. -""" -function compute_unl_inner(poly::PiecewiseLegendrePoly, wn) - wred = π / 4 * wn - phase_wi = phase_stable(poly, wn) - res = zero(ComplexF64) - @inbounds for order in axes(data(poly), 1), j in axes(data(poly), 2) - res += data(poly)[order, j] * get_tnl(order - 1, wred * Δx(poly)[j]) * phase_wi[j] / - norms(poly)[j] - end - return res / sqrt(2) +function xmax(poly::PiecewiseLegendrePoly) + return poly.xmax end -function compute_unl_inner(polys::PiecewiseLegendrePolyVector, wn) - p = reshape(range(0; length=polyorder(polys)), (1, :)) - wred = π / 4 * wn - phase_wi = phase_stable(polys, wn) - t_pin = permutedims(get_tnl.(p, wred .* Δx(polys)) .* phase_wi ./ - (sqrt(2) .* norms(polys))) - return [dot(poly.data, t_pin) for poly in polys] -end - -""" - get_tnl(l, w) - -Fourier integral of the `l`-th Legendre polynomial:: - Tₗ(ω) == ∫ dx exp(iωx) Pₗ(x) -""" -function get_tnl(l, w) - result = 2im^l * sphericalbesselj(l, abs(w)) - return w < 0 ? conj(result) : result +function xmin(poly::PiecewiseLegendrePolyVector) + return poly.xmin end -""" - shift_xmid(knots, Δx) - -Return midpoint relative to the nearest integer plus a shift. - -Return the midpoints `xmid` of the segments, as pair `(diff, shift)`, -where shift is in `(0, 1, -1)` and `diff` is a float such that -`xmid == shift + diff` to floating point accuracy. -""" -function shift_xmid(knots, Δx) - Δx_half = Δx ./ 2 - - xmid_m1 = cumsum(Δx) - xmid_m1 .-= Δx_half - - xmid_p1 = reverse!(cumsum(reverse(Δx))) - xmid_p1 .*= -1 - xmid_p1 .+= Δx_half - - xmid_0 = @inbounds knots[2:end] - xmid_0 .-= Δx_half - - shift = round.(Int, xmid_0) - diff = @inbounds [(xmid_m1, xmid_0, xmid_p1)[shift[i] + 2][i] for i in eachindex(shift)] - return diff, shift +function xmax(poly::PiecewiseLegendrePolyVector) + return poly.xmax end -""" - phase_stable(poly, wn) - -Phase factor for the piecewise Legendre to Matsubara transform. - -Compute the following phase factor in a stable way: - - exp.(iπ/2 * wn * cumsum(Δx(poly))) -""" -function phase_stable(poly, wn::Integer) - xmid_diff, extra_shift = shift_xmid(knots(poly), Δx(poly)) - @. im^mod(wn * (extra_shift + 1), 4) * cispi(wn * xmid_diff / 2) +function xmin(poly::PiecewiseLegendreFTVector) + return poly.xmin end -function phase_stable(poly, wn) - xmid_diff, extra_shift = shift_xmid(knots(poly), Δx(poly)) - - delta_wn, wn = modf(wn) - wn = trunc(Int, wn) - shift_arg = wn * xmid_diff - @. shift_arg += delta_wn * (extra_shift + xmid_diff) - - return @. im^mod(wn * (extra_shift + 1), 4) * cispi(shift_arg / 2) +function xmax(poly::PiecewiseLegendreFTVector) + return poly.xmax end diff --git a/src/sampling.jl b/src/sampling.jl index ef7f04c..25d0272 100644 --- a/src/sampling.jl +++ b/src/sampling.jl @@ -1,106 +1,151 @@ + """ - TauSampling <: AbstractSampling +TauSampling{T,B} <: AbstractSampling -Sparse sampling in imaginary time. +Sparse sampling in imaginary time using the C API. -Allows the transformation between the IR basis and a set of sampling points -in (scaled/unscaled) imaginary time. +Allows transformation between IR basis coefficients and sampling points in imaginary time. """ -struct TauSampling{T,TMAT,F,B} <: AbstractSampling{T,TMAT,F} - sampling_points :: Vector{T} - matrix :: Matrix{TMAT} - matrix_svd :: F - basis :: B +mutable struct TauSampling{T<:Real,B<:AbstractBasis} <: AbstractSampling{T,Float64,Nothing} + ptr::Ptr{spir_sampling} + sampling_points::Vector{T} + basis::B + + function TauSampling{T,B}(ptr::Ptr{spir_sampling}, sampling_points::Vector{T}, + basis::B) where {T<:Real,B<:AbstractBasis} + obj = new{T,B}(ptr, sampling_points, basis) + finalizer(s -> spir_sampling_release(s.ptr), obj) + return obj + end end +const TauSampling64F = TauSampling{Float64,FiniteTempBasis{Fermionic,LogisticKernel}} +const TauSampling64B = TauSampling{Float64,FiniteTempBasis{Bosonic,LogisticKernel}} + """ - TauSampling(basis; sampling_points=default_tau_sampling_points(basis), factorize=true) +MatsubaraSampling{T,B} <: AbstractSampling -Construct a `TauSampling` object. If not given, the `sampling_points` are chosen -as the extrema of the highest-order basis function in imaginary time. This turns -out to be close to optimal with respect to conditioning for this size (within a -few percent). `factorize` controls whether the SVD decomposition is computed. +Sparse sampling in Matsubara frequencies using the C API. + +Allows transformation between IR basis coefficients and sampling points in Matsubara frequencies. """ -function TauSampling(basis::AbstractBasis; - sampling_points=default_tau_sampling_points(basis), factorize=true) - matrix = eval_matrix(TauSampling, basis, sampling_points) - matrix_svd = factorize ? svd(matrix) : nothing - sampling = TauSampling(sampling_points, matrix, matrix_svd, basis) - if factorize && iswellconditioned(basis) && cond(sampling) > 1e8 - @warn "Sampling matrix is poorly conditioned (cond = $(cond(sampling)))." +mutable struct MatsubaraSampling{T<:MatsubaraFreq,B<:AbstractBasis} <: + AbstractSampling{T,ComplexF64,Nothing} + ptr::Ptr{spir_sampling} + sampling_points::Vector{T} + positive_only::Bool + basis::B + + function MatsubaraSampling{T,B}(ptr::Ptr{spir_sampling}, sampling_points::Vector{T}, + positive_only::Bool, basis::B) where {T<:MatsubaraFreq,B<:AbstractBasis} + obj = new{T,B}(ptr, sampling_points, positive_only, basis) + finalizer(s -> spir_sampling_release(s.ptr), obj) + return obj end - return sampling end -Base.getproperty(s::TauSampling, p::Symbol) = p === :τ ? sampling_points(s) : getfield(s, p) +const MatsubaraSampling64F = MatsubaraSampling{ + FermionicFreq,FiniteTempBasis{Fermionic,LogisticKernel}} +const MatsubaraSampling64B = MatsubaraSampling{ + BosonicFreq,FiniteTempBasis{Bosonic,LogisticKernel}} + +# Convenience constructors """ - MatsubaraSampling <: AbstractSampling + TauSampling(basis::AbstractBasis; sampling_points=nothing, use_positive_taus=false) -Sparse sampling in Matsubara frequencies. +Construct a `TauSampling` object from a basis. If `sampling_points` is not provided, +the default tau sampling points from the basis are used. -Allows the transformation between the IR basis and a set of sampling points -in (scaled/unscaled) imaginary frequencies. -""" -struct MatsubaraSampling{T<:MatsubaraFreq,TMAT,F,B<:AbstractBasis} <: - AbstractSampling{T,TMAT,F} - sampling_points :: Vector{T} - matrix :: Matrix{TMAT} - matrix_svd :: F - positive_only :: Bool - basis :: B -end +If `use_positive_taus=false` (default), the sampling points are in the range [-β/2, β/2]. +If `use_positive_taus=true`, the sampling points are folded to the positive tau domain [0, β). +This was the default behavior in SparseIR.jl of versions 1.x.x. """ - MatsubaraSampling(basis; positive_only=false, - sampling_points=default_matsubara_sampling_points(basis; positive_only), - factorize=true) +function TauSampling(basis::AbstractBasis; sampling_points=nothing, use_positive_taus=false) + @show use_positive_taus + if sampling_points === nothing + points = default_tau_sampling_points(basis) + if use_positive_taus + points = mod.(points, β(basis)) + sort!(points) + end + sampling_points = points + end + + # Create sampling object with C_API + status = Ref{Int32}(-100) + if !_is_column_major_contiguous(sampling_points) + error("Sampling points must be contiguous") + end + ptr = C_API.spir_tau_sampling_new( + _get_ptr(basis), length(sampling_points), sampling_points, status) + status[] == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to create tau sampling: status=$(status[])") + ptr != C_API.C_NULL || error("Failed to create tau sampling: null pointer returned") -Construct a `MatsubaraSampling` object. If not given, the `sampling_points` are chosen as -the (discrete) extrema of the highest-order basis function in Matsubara. This turns out -to be close to optimal with respect to conditioning for this size (within a few percent). + return TauSampling{Float64,typeof(basis)}(ptr, sampling_points, basis) +end -By setting `positive_only=true`, one assumes that functions to be fitted are symmetric in -Matsubara frequency, i.e.: +""" + MatsubaraSampling(basis::AbstractBasis; positive_only=false, sampling_points=nothing, factorize=true) -```math - Ĝ(iν) = conj(Ĝ(-iν)) -``` +Construct a `MatsubaraSampling` object from a basis. If `sampling_points` is not provided, +the default Matsubara sampling points from the basis are used. -or equivalently, that they are purely real in imaginary time. In this case, sparse sampling -is performed over non-negative frequencies only, cutting away half of the necessary sampling -space. `factorize` controls whether the SVD decomposition is computed. +If `positive_only=true`, assumes functions are symmetric in Matsubara frequency. """ -function MatsubaraSampling(basis::AbstractBasis; positive_only=false, - sampling_points=default_matsubara_sampling_points(basis; - positive_only), factorize=true) - sampling_points = if sampling_points isa AbstractRange - collect(sampling_points) +function MatsubaraSampling( + basis::AbstractBasis; positive_only=false, sampling_points=nothing) + if sampling_points === nothing + # Get default Matsubara sampling points from basis + status = Ref{Int32}(-100) + n_points = Ref{Int32}(-1) + + ret = C_API.spir_basis_get_n_default_matsus( + _get_ptr(basis), positive_only, n_points) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to get number of default Matsubara points") + + points_array = Vector{Int64}(undef, n_points[]) + ret = C_API.spir_basis_get_default_matsus( + _get_ptr(basis), positive_only, points_array) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to get default Matsubara points") + + # Convert to MatsubaraFreq objects based on statistics + if statistics(basis) isa Fermionic + sampling_points = [FermionicFreq(n) for n in points_array] + else + sampling_points = [BosonicFreq(n) for n in points_array] + end else - sampling_points + # Convert input to appropriate MatsubaraFreq type + if statistics(basis) isa Fermionic + sampling_points = [p isa FermionicFreq ? p : FermionicFreq(Int(p)) + for p in sampling_points] + else + sampling_points = [p isa BosonicFreq ? p : BosonicFreq(Int(p)) + for p in sampling_points] + end end - issorted(sampling_points) || sort!(sampling_points) - if positive_only - Int(first(sampling_points)) ≥ 0 || error("invalid negative sampling frequencies") - end - matrix = eval_matrix(MatsubaraSampling, basis, sampling_points) - has_zero = iszero(first(sampling_points)) - if factorize - svd_matrix = positive_only ? SplitSVD(matrix; has_zero) : svd(matrix) - else - svd_matrix = nothing - end - sampling = MatsubaraSampling(sampling_points, matrix, svd_matrix, positive_only, basis) - if factorize && iswellconditioned(basis) && cond(sampling) > 1e8 - @warn "Sampling matrix is poorly conditioned (cond = $(cond(sampling)))." - end - return sampling -end -function Base.getproperty(s::MatsubaraSampling, p::Symbol) - p === :ωn ? sampling_points(s) : getfield(s, p) + # Extract indices for C API + indices = [Int64(Int(p)) for p in sampling_points] + + status = Ref{Int32}(-100) + ptr = C_API.spir_matsu_sampling_new( + _get_ptr(basis), positive_only, length(indices), indices, status) + status[] == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to create Matsubara sampling: status=$(status[])") + ptr != C_NULL || error("Failed to create Matsubara sampling: null pointer returned") + + return MatsubaraSampling{eltype(sampling_points),typeof(basis)}( + ptr, sampling_points, positive_only, basis) end +# Common interface functions + """ eval_matrix(T, basis, x) @@ -111,279 +156,274 @@ eval_matrix(::Type{TauSampling}, basis, x) = permutedims(basis.u(x)) eval_matrix(::Type{MatsubaraSampling}, basis, x) = permutedims(basis.uhat(x)) """ - evaluate(sampling, al; dim=1) + npoints(sampling::AbstractSampling) -Evaluate the basis coefficients `al` at the sparse sampling points. +Get the number of sampling points. """ -function evaluate(smpl::AbstractSampling{S,Tmat}, al::AbstractArray{T,N}; - dim=1) where {S,Tmat,T,N} - if size(smpl.matrix, 2) ≠ size(al, dim) - msg = "Number of columns (got $(size(smpl.matrix, 2))) has to match al's size in dim (got $(size(al, dim)))." - throw(DimensionMismatch(msg)) - end - bufsize = ntuple(N) do d - d === dim ? size(smpl.matrix, 1) : size(al, d) - end - buffer = Array{promote_type(Tmat, T),N}(undef, bufsize) - return evaluate!(buffer, smpl, al; dim) +function npoints(sampling::Union{TauSampling,MatsubaraSampling}) + n_points = Ref{Int32}(-1) + ret = C_API.spir_sampling_get_npoints(sampling.ptr, n_points) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to get number of sampling points") + return Int(n_points[]) end +# Evaluation and fitting functions + """ - evaluate!(buffer::AbstractArray{T,N}, sampling, al; dim=1) where {T,N} + evaluate(sampling::AbstractSampling, al::Array; dim=1) + +Evaluate basis coefficients at the sampling points using the C API. -Like [`evaluate`](@ref), but write the result to `buffer`. -Please use dim = 1 or N to avoid allocating large temporary arrays internally. +For multidimensional arrays, `dim` specifies which dimension corresponds to the basis coefficients. """ -function evaluate!(buffer::AbstractArray{T,N}, smpl::AbstractSampling, - al::AbstractArray{S,N}; dim=1) where {S,T,N} - resultsize = ntuple(j -> j == dim ? size(smpl.matrix, 1) : size(al, j), N) - if size(buffer) ≠ resultsize - msg = "Buffer has the wrong size (got $(size(buffer)), expected $resultsize)." - throw(DimensionMismatch(msg)) +function evaluate( + sampling::Union{TauSampling,MatsubaraSampling}, al::Array{ + T,N}; dim=1) where {T,N} + # Determine output dimensions + output_dims = collect(size(al)) + output_dims[dim] = npoints(sampling) + + # Determine output type based on sampling type + if sampling isa TauSampling + # For complex input, TauSampling should produce complex output + output_type = T + output = Array{output_type,N}(undef, output_dims...) + evaluate!(output, sampling, al; dim=dim) + else # MatsubaraSampling + output_type = T <: Real ? ComplexF64 : promote_type(ComplexF64, T) + output = Array{output_type,N}(undef, output_dims...) + evaluate!(output, sampling, al; dim=dim) end - return matop_along_dim!(buffer, smpl.matrix, al, dim, mul!) + + return output end """ - fit(sampling, al::AbstractArray{T,N}; dim=1) + evaluate!(output::Array, sampling::AbstractSampling, al::Array; dim=1) -Fit basis coefficients from the sparse sampling points -Please use dim = 1 or N to avoid allocating large temporary arrays internally. +In-place version of [`evaluate`](@ref). Write results to the pre-allocated `output` array. """ -function fit(smpl::AbstractSampling{S,Tmat}, al::AbstractArray{T,N}; - dim=1) where {S,Tmat,T,N} - if size(smpl.matrix, 1) ≠ size(al, dim) - msg = "Number of rows (got $(size(smpl.matrix, 1))) " - "has to match al's size in dim (got $(size(al, dim)))." - throw(DimensionMismatch(msg)) +function evaluate!( + output::Array{Tout,N}, sampling::TauSampling, al::Array{ + Tin,N}; dim=1) where {Tout,Tin,N} + # Check dimensions + expected_dims = collect(size(al)) + expected_dims[dim] = npoints(sampling) + size(output) == tuple(expected_dims...) || + throw(DimensionMismatch("Output array has wrong dimensions")) + + # Prepare arguments for C API + ndim = N + input_dims = Int32[size(al)...] + target_dim = Int32(dim - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if !_is_column_major_contiguous(al) + error("Input array must be contiguous") end - bufsize = ntuple(N) do d - d === dim ? size(smpl.matrix, 2) : size(al, d) + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") end - buffer = Array{promote_type(Tmat, T),N}(undef, bufsize) - return fit!(buffer, smpl, al; dim) -end -""" - workarrlength(smpl::AbstractSampling, al; dim=1) + # Call appropriate C function based on input/output types + if Tin <: Real && Tout <: Real + ret = C_API.spir_sampling_eval_dd( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + elseif Tin <: Complex && Tout <: Complex + ret = C_API.spir_sampling_eval_zz( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + else + error("Type combination not yet supported for TauSampling: input=$Tin, output=$Tout") + end -Return length of workarr for `fit!`. -""" -function workarrlength(smpl::AbstractSampling, al::AbstractArray; dim=1) - length(smpl.matrix_svd.S) * (length(al) ÷ size(al, dim)) + ret in [ + C_API.SPIR_INPUT_DIMENSION_MISMATCH, + C_API.SPIR_OUTPUT_DIMENSION_MISMATCH, + C_API.SPIR_INVALID_DIMENSION + ] && throw(DimensionMismatch("Failed to evaluate sampling: status=$ret")) + return output end -""" - fit!(buffer::Array{S,N}, smpl::AbstractSampling, al::Array{T,N}; - dim=1, workarr::Vector{S}) where {S,T,N} +function evaluate!(output::Array{Tout,N}, sampling::MatsubaraSampling, + al::Array{Tin,N}; dim=1) where {Tout,Tin,N} + # Check dimensions + expected_dims = collect(size(al)) + expected_dims[dim] = npoints(sampling) + size(output) == tuple(expected_dims...) || + throw(DimensionMismatch("Output array has wrong dimensions")) + + # Prepare arguments for C API + ndim = N + input_dims = Int32[size(al)...] + target_dim = Int32(dim - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if !_is_column_major_contiguous(al) + error("Input array must be contiguous") + end + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") + end -Like [`fit`](@ref), but write the result to `buffer`. -Use `dim = 1` or `dim = N` to avoid allocating large temporary arrays internally. -The length of `workarr` cannot be smaller than [`SparseIR.workarrlength`](@ref)`(smpl, al)`. -""" -function fit!(buffer::Array{S,N}, smpl::AbstractSampling, al::Array{T,N}; dim=1, - workarr::Vector{S}=Vector{S}(undef, workarrlength(smpl, al; dim))) where {S,T,N} - resultsize = ntuple(j -> j == dim ? size(smpl.matrix, 2) : size(al, j), N) - if size(buffer) ≠ resultsize - msg = "Buffer has the wrong size (got $(size(buffer)), expected $resultsize)." - throw(DimensionMismatch(msg)) + # Call appropriate C function based on input/output types + if Tin <: Real && Tout <: Complex + ret = C_API.spir_sampling_eval_dz( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + elseif Tin <: Complex && Tout <: Complex + ret = C_API.spir_sampling_eval_zz( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + else + error("Type combination not supported for MatsubaraSampling: input=$Tin, output=$Tout") end - length(workarr) ≥ workarrlength(smpl, al; dim) || - throw(ArgumentError("workarr too small")) - return div_noalloc!(buffer, smpl.matrix_svd, al, workarr, dim) + + ret in [ + C_API.SPIR_INPUT_DIMENSION_MISMATCH, + C_API.SPIR_OUTPUT_DIMENSION_MISMATCH, + C_API.SPIR_INVALID_DIMENSION + ] && throw(DimensionMismatch("Failed to evaluate sampling: status=$ret")) + return output end """ - movedim(arr::AbstractArray, src => dst) + fit(sampling::AbstractSampling, al::Array; dim=1) -Move `arr`'s dimension at `src` to `dst` while keeping the order of the remaining -dimensions unchanged. -""" -function movedim(arr::AbstractArray{T,N}, dims::Pair) where {T,N} - src, dst = dims - src == dst && return arr - return permutedims(arr, getperm(N, dims)) -end - -function getperm(N, dims::Pair) - src, dst = dims - perm = collect(1:N) - deleteat!(perm, src) - insert!(perm, dst, src) - return perm -end +Fit basis coefficients from values at sampling points using the C API. +For multidimensional arrays, `dim` specifies which dimension corresponds to the sampling points. """ - matop_along_dim!(buffer, mat, arr::AbstractArray, dim::Integer, op) +function fit( + sampling::Union{TauSampling,MatsubaraSampling}, al::Array{T,N}; dim=1) where { + T,N} + # Determine output dimensions + output_dims = collect(size(al)) + output_dims[dim] = length(sampling.basis) + + # Determine output type - typically real for coefficients + if sampling isa TauSampling + # For complex input, we need complex output + output_type = T + else # MatsubaraSampling + # For Matsubara sampling, we need to be careful about type matching + # The C API might expect complex output even for real input + output_type = T <: Complex ? T : ComplexF64 + end -Apply the operator `op` to the matrix `mat` and to the array `arr` along the dimension -`dim`, writing the result to `buffer`. -""" -function matop_along_dim!(buffer, mat, arr::AbstractArray{T,N}, dim, op) where {T,N} - 1 ≤ dim ≤ N || throw(DomainError(dim, "Dimension must be in [1, $N]")) - - if dim == 1 - matop!(buffer, mat, arr, op, 1) - elseif dim != N - # Move the target dim to the first position - perm = getperm(N, dim => 1) - arr_perm = permutedims(arr, perm) - buffer_perm = permutedims(buffer, perm) - matop!(buffer_perm, mat, arr_perm, op, 1) - permutedims!(buffer, buffer_perm, getperm(N, 1 => dim)) - else - # Apply the operator to the last dimension - matop!(buffer, mat, arr, op, N) + output = Array{output_type,N}(undef, output_dims...) + fit!(output, sampling, al; dim=dim) + + # For MatsubaraSampling, if we want real coefficients, extract real part + if sampling isa MatsubaraSampling && T <: Complex && output_type <: Complex + # The fitted coefficients should be real for physical reasons + # Extract real part and return as real array + real_output = Array{real(output_type),N}(undef, output_dims...) + real_output .= real.(output) + return real_output end - return buffer + + return output end """ - matop!(buffer, mat, arr::AbstractArray, op, dim) + fit!(output::Array, sampling::AbstractSampling, al::Array; dim=1) -Apply the operator `op` to the matrix `mat` and to the array `arr` along the first -dimension (dim=1) or the last dimension (dim=N). +In-place version of [`fit`](@ref). Write results to the pre-allocated `output` array. """ -function matop!(buffer::AbstractArray{S,N}, mat, arr::AbstractArray{T,N}, op, - dim) where {S,T,N} - if dim == 1 - flatarr = reshape(arr, (size(arr, 1), :)) - flatbuffer = reshape(buffer, (size(buffer, 1), :)) - op(flatbuffer, mat, flatarr) - elseif dim == N - flatarr = reshape(arr, (:, size(arr, N))) - flatbuffer = reshape(buffer, (:, size(buffer, N))) - op(flatbuffer, flatarr, transpose(mat)) - else - throw(DomainError(dim, "Dimension must be 1 or $N.")) +function fit!( + output::Array{Tout,N}, sampling::TauSampling, al::Array{ + Tin,N}; dim=1) where {Tout,Tin,N} + # Check dimensions + expected_dims = collect(size(al)) + expected_dims[dim] = length(sampling.basis) + size(output) == tuple(expected_dims...) || + throw(DimensionMismatch("Output array has wrong dimensions")) + + # Prepare arguments for C API + ndim = N + input_dims = Int32[size(al)...] + target_dim = Int32(dim - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if !_is_column_major_contiguous(al) + error("Input array must be contiguous") + end + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") end - return buffer -end -function div_noalloc!(buffer::AbstractArray{S,N}, mat, arr::AbstractArray{T,N}, workarr, - dim) where {S,T,N} - 1 ≤ dim ≤ N || throw(DomainError(dim, "Dimension must be in [1, $N]")) - - if dim == 1 - flatarr = reshape(arr, (size(arr, 1), :)) - flatbuffer = reshape(buffer, (size(buffer, 1), :)) - ldiv_noalloc!(flatbuffer, mat, flatarr, workarr) - elseif dim != N - # Move the target dim to the first position - arr_perm = movedim(arr, dim => 1) - buffer_perm = movedim(buffer, dim => 1) - flatarr = reshape(arr_perm, (size(arr_perm, 1), :)) - flatbuffer = reshape(buffer_perm, (size(buffer_perm, 1), :)) - ldiv_noalloc!(flatbuffer, mat, flatarr, workarr) - buffer .= movedim(buffer_perm, 1 => dim) + # Call appropriate C function + if Tin <: Real && Tout <: Real + ret = C_API.spir_sampling_fit_dd( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + elseif Tin <: Complex && Tout <: Complex + ret = C_API.spir_sampling_fit_zz( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) else - flatarr = reshape(arr, (:, size(arr, N))) - flatbuffer = reshape(buffer, (:, size(buffer, N))) - rdiv_noalloc!(flatbuffer, flatarr, mat, workarr) + ArgumentError("Type combination not yet supported for TauSampling fit: input=$Tin, output=$Tout") end - return buffer -end - -function ldiv_noalloc!(Y::AbstractMatrix, A::SVD, B::AbstractMatrix, workarr) - # Setup work space - worksize = (size(A.U, 2), size(B, 2)) - worklength = prod(worksize) - length(workarr) ≥ worklength || - throw(DimensionMismatch("size(workarr)=$(size(workarr)), min worksize=$worklength")) - workarr_view = reshape(view(workarr, 1:worklength), worksize) - - mul!(workarr_view, A.U', B) - workarr_view ./= A.S - return mul!(Y, A.V, workarr_view) -end -function rdiv_noalloc!(Y::AbstractMatrix, A::AbstractMatrix, B::SVD, workarr) - # Setup work space - worksize = (size(A, 1), size(B.U, 2)) - worklength = prod(worksize) - length(workarr) ≥ worklength || - throw(DimensionMismatch("size(workarr)=$(size(workarr)), min worksize=$worklength")) - workarr_view = reshape(view(workarr, 1:worklength), worksize) - - # Note: conj creates a temporary matrix - mul!(workarr_view, A, conj(B.U)) - workarr_view ./= reshape(B.S, 1, :) - return mul!(Y, workarr_view, conj(B.Vt)) + ret in [ + C_API.SPIR_INPUT_DIMENSION_MISMATCH, + C_API.SPIR_OUTPUT_DIMENSION_MISMATCH, + C_API.SPIR_INVALID_DIMENSION + ] && throw(DimensionMismatch("Failed to fit sampling: status=$ret")) + return output end -struct SplitSVD{T} - A::Matrix{Complex{T}} - UrealT::Matrix{T} - UimagT::Matrix{T} - S::Vector{T} - V::Matrix{T} -end - -function SplitSVD(a::Matrix{<:Complex}, - (u, s, - v)::Tuple{AbstractMatrix{<:Complex}, - AbstractVector{<:Real}, - AbstractMatrix{<:Real}}) - if any(iszero, s) - nonzero = findall(!iszero, s) - u, s, v = u[:, nonzero], s[nonzero], v[nonzero, :] +function fit!( + output::Array{Tout,N}, sampling::MatsubaraSampling, al::Array{ + Tin,N}; dim=1) where {Tout,Tin,N} + # Check dimensions + expected_dims = collect(size(al)) + expected_dims[dim] = length(sampling.basis) + size(output) == tuple(expected_dims...) || + throw(DimensionMismatch("Output array has wrong dimensions")) + + # Prepare arguments for C API + ndim = N + input_dims = Int32[size(al)...] + target_dim = Int32(dim - 1) # C uses 0-based indexing + order = C_API.SPIR_ORDER_COLUMN_MAJOR + + if !_is_column_major_contiguous(al) + error("Input array must be contiguous") + end + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") end - ut = transpose(u) - SplitSVD(a, real(ut), imag(ut), s, copy(v)) -end - -SplitSVD(a::Matrix{<:Complex}; has_zero=false) = SplitSVD(a, split_complex(a; has_zero)) - -function ldiv_noalloc!(Y::AbstractMatrix, A::SplitSVD, B::AbstractMatrix, workarr) - # Setup work space - worksize = (size(A.UrealT, 1), size(B, 2)) - worklength = prod(worksize) - length(workarr) ≥ worklength || - throw(DimensionMismatch("size(workarr)=$(size(workarr)), min worksize=$worklength")) - workarr_view = reshape(view(workarr, 1:worklength), worksize) - - mul!(workarr_view, A.UrealT, real(B)) - mul!(workarr_view, A.UimagT, imag(B), true, true) - workarr_view ./= A.S - return mul!(Y, A.V, workarr_view) -end - -function rdiv_noalloc!(Y::AbstractMatrix, A::AbstractMatrix, B::SplitSVD, workarr) - error("not yet implemented") -end - -function split_complex(mat::Matrix{<:Complex}; has_zero=false, svd_algo=svd) - # split real and imaginary part into separate matrices - offset_imag = has_zero ? 2 : 1 - rmat = [real(mat) - imag(mat)[offset_imag:end, :]] - - # perform real-valued SVD - ur, s, v = svd_algo(rmat) - # undo the split of the resulting ur matrix - n = size(mat, 1) - u = complex(ur[1:n, :]) - u[offset_imag:end, :] .+= im .* ur[(n + 1):end, :] - return u, s, v -end + # Call appropriate C function based on input/output types + if Tin <: Complex && Tout <: Complex + # Use complex-to-complex API and then extract real part if needed + ret = C_API.spir_sampling_fit_zz( + sampling.ptr, order, ndim, input_dims, target_dim, al, output) + elseif Tin <: Complex && Tout <: Real + # Create temporary complex output, then extract real part + temp_output = Array{ComplexF64,N}(undef, size(output)...) + ret = C_API.spir_sampling_fit_zz( + sampling.ptr, order, ndim, input_dims, target_dim, al, temp_output) + ret == C_API.SPIR_COMPUTATION_SUCCESS || + error("Failed to fit sampling: status=$ret") + output .= real.(temp_output) + return output + else + error("Type combination not supported for MatsubaraSampling fit: input=$Tin, output=$Tout") + end -const MatsubaraSampling64F = @static if VERSION ≥ v"1.8-" - MatsubaraSampling{FermionicFreq,ComplexF64, - SVD{ComplexF64,Float64,Matrix{ComplexF64},Vector{Float64}}} -else - MatsubaraSampling{FermionicFreq,ComplexF64,SVD{ComplexF64,Float64,Matrix{ComplexF64}}} + ret in [ + C_API.SPIR_INPUT_DIMENSION_MISMATCH, + C_API.SPIR_OUTPUT_DIMENSION_MISMATCH, + C_API.SPIR_INVALID_DIMENSION + ] && throw(DimensionMismatch("Failed to fit sampling: status=$ret")) + return output end -const MatsubaraSampling64B = @static if VERSION ≥ v"1.8-" - MatsubaraSampling{BosonicFreq,ComplexF64, - SVD{ComplexF64,Float64,Matrix{ComplexF64},Vector{Float64}}} -else - MatsubaraSampling{BosonicFreq,ComplexF64,SVD{ComplexF64,Float64,Matrix{ComplexF64}}} +# Convenience property accessors (similar to SparseIR.jl) +function Base.getproperty(s::TauSampling, p::Symbol) + p === :tau ? sampling_points(s) : + getfield(s, p) end - -const TauSampling64 = @static if VERSION ≥ v"1.8-" - TauSampling{Float64,Float64,SVD{Float64,Float64,Matrix{Float64},Vector{Float64}}} -else - TauSampling{Float64,Float64,SVD{Float64,Float64,Matrix{Float64}}} +function Base.getproperty(s::MatsubaraSampling, p::Symbol) + p === :ωn ? sampling_points(s) : + getfield(s, p) end diff --git a/src/svd.jl b/src/svd.jl deleted file mode 100644 index eadb1fe..0000000 --- a/src/svd.jl +++ /dev/null @@ -1,29 +0,0 @@ -const T_MAX = Float64x2 - -function compute_svd(A::AbstractMatrix{T_MAX}; n_sv_hint=nothing, strategy=:default) - if !isnothing(n_sv_hint) - @info "n_sv_hint is set but will not be used in the current implementation!" - end - if strategy !== :default - @info "strategy is set but will not be used in the current implementation!" - end - - u, s, v = tsvd(A) - return u, s, v -end - -function compute_svd(A::AbstractMatrix; n_sv_hint=nothing, strategy=:default) - if !isnothing(n_sv_hint) - @info "n_sv_hint is set but will not be used in the current implementation!" - end - - if strategy === :default - u, s, v = svd(A) - elseif strategy === :accurate - u, s, v = svd(A; alg=QRIteration()) - else - throw(DomainError(strategy, "unknown strategy")) - end - - return u, s, v -end diff --git a/src/sve.jl b/src/sve.jl index f4f6762..ac03714 100644 --- a/src/sve.jl +++ b/src/sve.jl @@ -1,101 +1,3 @@ -using LinearAlgebra - -""" - SamplingSVE <: AbstractSVE - -SVE to SVD translation by sampling technique [1]. - -Maps the singular value expansion (SVE) of a kernel `kernel` onto the singular -value decomposition of a matrix `A`. This is achieved by choosing two -sets of Gauss quadrature rules: `(x, wx)` and `(y, wy)` and -approximating the integrals in the SVE equations by finite sums. This -implies that the singular values of the SVE are well-approximated by the -singular values of the following matrix: - - A[i, j] = √(wx[i]) * K(x[i], y[j]) * √(wy[j]) - -and the values of the singular functions at the Gauss sampling points can -be reconstructed from the singular vectors `u` and `v` as follows: - - u[l,i] ≈ √(wx[i]) u[l](x[i]) - v[l,j] ≈ √(wy[j]) u[l](y[j]) - -[1] P. Hansen, Discrete Inverse Problems, Ch. 3.1 -""" -struct SamplingSVE{T<:AbstractFloat,K<:AbstractKernel} <: AbstractSVE - kernel :: K - ε :: Float64 - n_gauss :: Int - nsvals_hint :: Int - - rule :: Rule{T} - segs_x :: Vector{T} - segs_y :: Vector{T} - gauss_x :: Rule{T} - gauss_y :: Rule{T} -end - -function SamplingSVE(kernel, ε, (::Type{T})=Float64; n_gauss=nothing) where {T} - sve_hints_ = sve_hints(kernel, ε) - n_gauss = something(n_gauss, ngauss(sve_hints_)) - rule = legendre(n_gauss, T) - segs_x, segs_y = segments_x(sve_hints_, T), segments_y(sve_hints_, T) - gauss_x, gauss_y = piecewise(rule, segs_x), piecewise(rule, segs_y) - - return SamplingSVE(kernel, ε, n_gauss, nsvals(sve_hints_), - rule, segs_x, segs_y, gauss_x, gauss_y) -end - -""" - CentrosymmSVE <: AbstractSVE - -SVE of centrosymmetric kernel in block-diagonal (even/odd) basis. - -For a centrosymmetric kernel `K`, i.e., a kernel satisfying: -`K(x, y) == K(-x, -y)`, one can make the following ansatz for the -singular functions: - - u[l](x) = ured[l](x) + sign[l] * ured[l](-x) - v[l](y) = vred[l](y) + sign[l] * ured[l](-y) - -where `sign[l]` is either `+1` or `-1`. This means that the singular value -expansion can be block-diagonalized into an even and an odd part by -(anti-)symmetrizing the kernel: - - K_even = K(x, y) + K(x, -y) - K_odd = K(x, y) - K(x, -y) - -The `l`th basis function, restricted to the positive interval, is then -the singular function of one of these kernels. If the kernel generates a -Chebyshev system [1], then even and odd basis functions alternate. - -[1]: A. Karlin, Total Positivity (1968). -""" -struct CentrosymmSVE{K<:AbstractKernel,SVEEVEN<:AbstractSVE,SVEODD<:AbstractSVE} <: - AbstractSVE - kernel :: K - ε :: Float64 - even :: SVEEVEN - odd :: SVEODD - nsvals_hint :: Int -end - -function CentrosymmSVE(kernel, ε, ::Type{T}; InnerSVE=SamplingSVE, - n_gauss=nothing) where {T} - even = InnerSVE(get_symmetrized(kernel, +1), ε, T; n_gauss) - odd = InnerSVE(get_symmetrized(kernel, -1), ε, T; n_gauss) - return CentrosymmSVE(kernel, ε, even, odd, max(even.nsvals_hint, odd.nsvals_hint)) -end - -struct SVEResult{K} - u::PiecewiseLegendrePolyVector - s::Vector{Float64} - v::PiecewiseLegendrePolyVector - - kernel::K - ε::Float64 -end - """ SVEResult(kernel::AbstractKernel; Twork=nothing, ε=nothing, lmax=typemax(Int), @@ -123,24 +25,19 @@ using a collocation). - `K::AbstractKernel`: Integral kernel to take SVE from. - - `ε::Real`: Accuracy target for the basis: attempt to have singular values down - to a relative magnitude of `ε`, and have each singular value - and singular vector be accurate to `ε`. A `Twork` with - a machine epsilon of `ε^2` or lower is required to satisfy - this. Defaults to `2.2e-16` if xprec is available, and `1.5e-8` - otherwise. - - `cutoff::Real`: Relative cutoff for the singular values. A `Twork` with - machine epsilon of `cutoff` is required to satisfy this. - Defaults to a small multiple of the machine epsilon. - - Note that `cutoff` and `ε` serve distinct purposes. `cutoff` - represents the accuracy to which the kernel is reproduced, whereas - `ε` is the accuracy to which the singular values and vectors - are guaranteed. + - `ϵ::Real`: Relative cutoff for the singular values. Only singular values + with relative magnitude ≥ `cutoff` are kept. Defaults to `eps(Float64)` (≈ 2.22e-16). + - `cutoff::Real`: Accuracy target for the basis. Controls the precision to which + singular values and singular vectors are computed. Defaults to `NaN` (uses internal default). - `lmax::Integer`: Maximum basis size. If given, only at most the `lmax` most significant singular values and associated singular functions are returned. - `n_gauss (int): Order of Legendre polynomials. Defaults to kernel hinted value. - - `Twork``: Working data type. Defaults to a data type with machine epsilon of at most `ε^2`and at most `cutoff`, or otherwise most accurate data type available. + - `Twork::Integer`: Working data type. Defaults to `SPIR_TWORK_AUTO` which automatically selects the appropriate precision based on the accuracy requirements. + Available options: + + + `SPIR_TWORK_AUTO`: Automatically select the best precision (default) + + `SPIR_TWORK_FLOAT64`: Use double precision (64-bit) + + `SPIR_TWORK_FLOAT64X2`: Use extended precision (128-bit) - `sve_strat::AbstractSVE`: SVE to SVD translation strategy. Defaults to `SamplingSVE`, optionally wrapped inside of a `CentrosymmSVE` if the kernel is centrosymmetric. - `svd_strat` ('fast' or 'default' or 'accurate'): SVD solver. Defaults to fast @@ -150,201 +47,24 @@ using a collocation). Returns: An `SVEResult` containing the truncated singular value expansion. """ -function SVEResult(kernel::AbstractKernel; - Twork=nothing, cutoff=nothing, ε=nothing, lmax=typemax(Int), - n_gauss=nothing, svd_strat=:auto, - SVEstrat=iscentrosymmetric(kernel) ? CentrosymmSVE : SamplingSVE) - safe_ε, Twork_actual, svd_strat = choose_accuracy(ε, Twork, svd_strat) - sve = SVEstrat(kernel, safe_ε, Twork_actual; n_gauss) - - svds = compute_svd.(matrices(sve); strategy=svd_strat) - u_, s_, v_ = zip(svds...) - cutoff_actual = something(cutoff, 2eps(Twork_actual)) - u, s, v = truncate(u_, s_, v_; rtol=cutoff_actual, lmax) - return postprocess(sve, u, s, v) -end - -function part(sve::SVEResult; ε=nothing, max_size=nothing) - ε = something(ε, sve.ε) - cut = count(≥(ε * first(sve.s)), sve.s) - if !isnothing(max_size) - cut = min(cut, max_size) - end - return sve.u[1:cut], sve.s[1:cut], sve.v[1:cut] -end - -Base.iterate(sve::SVEResult, n=1) = n ≤ 3 ? ((sve.u, sve.s, sve.v)[n], n + 1) : nothing - -""" - matrices(sve::AbstractSVE) - -SVD problems underlying the SVE. -""" -function matrices(sve::SamplingSVE) - result = matrix_from_gauss(sve.kernel, sve.gauss_x, sve.gauss_y) - result .*= sqrt.(sve.gauss_x.w) - result .*= sqrt.(permutedims(sve.gauss_y.w)) - return (result,) -end -matrices(sve::CentrosymmSVE) = (only(matrices(sve.even)), only(matrices(sve.odd))) - -""" - postprocess(sve::AbstractSVE, u, s, v) - -Construct the SVE result from the SVD. -""" -function postprocess(sve::SamplingSVE, (u,), (s,), (v,)) - s = Float64.(s) - u_x = u ./ sqrt.(sve.gauss_x.w) - v_y = v ./ sqrt.(sve.gauss_y.w) - - u_x = reshape(u_x, (sve.n_gauss, length(sve.segs_x) - 1, length(s))) - v_y = reshape(v_y, (sve.n_gauss, length(sve.segs_y) - 1, length(s))) - - cmat = legendre_collocation(sve.rule) - u_data = reshape(cmat * reshape(u_x, (size(u_x, 1), :)), - (:, size(u_x, 2), size(u_x, 3))) - v_data = reshape(cmat * reshape(v_y, (size(v_y, 1), :)), - (:, size(v_y, 2), size(v_y, 3))) - - dsegs_x = diff(sve.segs_x) - dsegs_y = diff(sve.segs_y) - u_data .*= sqrt.(0.5 .* reshape(dsegs_x, (1, :))) - v_data .*= sqrt.(0.5 .* reshape(dsegs_y, (1, :))) - - # Construct polynomials - ulx = PiecewiseLegendrePolyVector(Float64.(u_data), Float64.(sve.segs_x)) - vly = PiecewiseLegendrePolyVector(Float64.(v_data), Float64.(sve.segs_y)) - canonicalize!(ulx, vly) - return SVEResult(ulx, s, vly, sve.kernel, sve.ε) -end - -function postprocess(sve::CentrosymmSVE, u, s, v) - u_even, s_even, v_even = postprocess(sve.even, u[1:1], s[1:1], v[1:1]) - u_odd, s_odd, v_odd = postprocess(sve.odd, u[2:2], s[2:2], v[2:2]) - - # Merge two sets - u = PiecewiseLegendrePolyVector([u_even; u_odd]) - v = PiecewiseLegendrePolyVector([v_even; v_odd]) - s = [s_even; s_odd] - signs = [fill(1, length(s_even)); fill(-1, length(s_odd))] - - # Sort: now for totally positive kernels like defined in this module, - # this strictly speaking is not necessary as we know that the even/odd - # functions intersperse. - sort = sortperm(s; rev=true) - u = u[sort] - v = v[sort] - s = s[sort] - signs = signs[sort] - - # Extend to the negative side - u_complete = similar(u) - v_complete = similar(v) - full_hints = sve_hints(sve.kernel, sve.ε) - segs_x = segments_x(full_hints) - segs_y = segments_y(full_hints) - - poly_flip_x = (-1) .^ range(0; length=size(first(u).data, 1)) - for i in eachindex(u, v) - u_pos_data = u[i].data / sqrt(2) - v_pos_data = v[i].data / sqrt(2) - - u_neg_data = reverse(u_pos_data; dims=2) .* poly_flip_x * signs[i] - v_neg_data = reverse(v_pos_data; dims=2) .* poly_flip_x * signs[i] - u_data = hcat(u_neg_data, u_pos_data) - v_data = hcat(v_neg_data, v_pos_data) - u_complete[i] = PiecewiseLegendrePoly(u_data, segs_x, i - 1; symm=signs[i]) - v_complete[i] = PiecewiseLegendrePoly(v_data, segs_y, i - 1; symm=signs[i]) - end - - return SVEResult(u_complete, s, v_complete, sve.kernel, sve.ε) -end - -""" - choose_accuracy(ε, Twork[, svd_strat]) +mutable struct SVEResult{K<:AbstractKernel} + ptr::Ptr{spir_sve_result} + kernel::K + function SVEResult( + kernel::K, ε::Real=eps(Float64); cutoff::Real=NaN, lmax::Integer=typemax(Int32), + n_gauss::Integer=-1, Twork::Integer=SPIR_TWORK_AUTO) where {K<:AbstractKernel} -Choose work type and accuracy based on specs and defaults -""" -function choose_accuracy(ε, Twork, svd_strat) - ε, Twork, auto_svd_strat = choose_accuracy(ε, Twork) - svd_strat === :auto && (svd_strat = auto_svd_strat) - return ε, Twork, svd_strat -end -function choose_accuracy(ε, Twork) - if ε ≥ sqrt(eps(Twork)) - return ε, Twork, :default - else - @warn """Basis cutoff is $ε, which is below √ε with ε = $(eps(Twork)). - Expect singular values and basis functions for large l to have lower precision - than the cutoff.""" - return ε, Twork, :accurate - end -end -function choose_accuracy(ε, ::Nothing) - if ε ≥ sqrt(eps(Float64)) - return ε, Float64, :default - else - if ε < sqrt(eps(T_MAX)) - @warn """Basis cutoff is $ε, which is below √ε with ε = $(eps(T_MAX)). - Expect singular values and basis functions for large l to have lower precision - than the cutoff.""" + # check Twork + if Twork ∉ [SPIR_TWORK_AUTO, SPIR_TWORK_FLOAT64, SPIR_TWORK_FLOAT64X2] + error("Invalid Twork value: $Twork") end - return ε, T_MAX, :default - end -end -choose_accuracy(::Nothing, Twork) = sqrt(eps(Twork)), Twork, :default -choose_accuracy(::Nothing, ::Nothing) = Float64(sqrt(eps(T_MAX))), T_MAX, :default - -""" - canonicalize!(u, v) - -Canonicalize basis. - -Each SVD `(u[l], v[l])` pair is unique only up to a global phase, which may -differ from implementation to implementation and also platform. We -fix that gauge by demanding `u[l](1) > 0`. This ensures a diffeomorphic -connection to the Legendre polynomials as `Λ → 0`. -""" -function canonicalize!(ulx, vly) - for i in eachindex(ulx, vly) - gauge = sign(ulx[i](1)) - ulx[i].data .*= gauge - vly[i].data .*= gauge - end -end - -""" - truncate(u, s, v; rtol=0.0, lmax=typemax(Int)) - -Truncate singular value expansion. - -# Arguments - - `u`, `s`, `v`: Thin singular value expansion - - `rtol`: Only singular values satisfying `s[l]/s[1] > rtol` are retained. - - `lmax`: At most the `lmax` most significant singular values are retained. -""" -function truncate(u, s, v; rtol=0.0, lmax=typemax(Int)) - lmax ≥ 0 || throw(DomainError(lmax, "lmax must be non-negative")) - 0 ≤ rtol ≤ 1 || throw(DomainError(rtol, "rtol must be in [0, 1]")) - - sall = sort!(vcat(s...); rev=true) - - # Determine singular value cutoff. Note that by selecting a cutoff even - # in the case of lmax, we make sure to never remove parts of a degenerate - # singular value space, rather, we reduce the size of the basis. - cutoff = if lmax < length(sall) - max(rtol * first(sall), sall[lmax]) - else - rtol * first(sall) + status = Ref{Int32}(-100) + sve_result = spir_sve_result_new( + kernel.ptr, ε, cutoff, lmax, n_gauss, Twork, status) + status[] == 0 || error("Failed to create SVEResult") + result = new{K}(sve_result, kernel) + finalizer(r -> spir_sve_result_release(r.ptr), result) + return result end - - # Determine how many singular values survive in each group - scount = map(si -> count(>(cutoff), si), s) - - u_cut = map((ui, scounti) -> ui[:, 1:scounti], u, scount) - s_cut = map((si, scounti) -> si[1:scounti], s, scount) - v_cut = map((vi, scounti) -> vi[:, 1:scounti], v, scount) - return u_cut, s_cut, v_cut end diff --git a/test/C_API/cinterface_core_tests.jl b/test/C_API/cinterface_core_tests.jl new file mode 100644 index 0000000..82fa0b1 --- /dev/null +++ b/test/C_API/cinterface_core_tests.jl @@ -0,0 +1,324 @@ +# Tests corresponding to test/cpp/cinterface_core.cxx +# Tests for kernel accuracy, basis constructors, and basis functions + +@testitem "Kernel Accuracy Tests" tags=[:cinterface] begin + using SparseIR + + # Test individual kernels (corresponds to cinterface_core.cxx TEST_CASE "Kernel Accuracy Tests") + + @testset "LogisticKernel(9)" begin + Lambda = 9.0 + status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(Lambda, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test kernel != C_NULL + SparseIR.spir_kernel_release(kernel) + end + + @testset "RegularizedBoseKernel(10)" begin + Lambda = 10.0 + status = Ref{Int32}(0) + kernel = SparseIR.spir_reg_bose_kernel_new(Lambda, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test kernel != C_NULL + SparseIR.spir_kernel_release(kernel) + end + + @testset "Kernel Domain" begin + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(9.0, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test kernel != C_NULL + + # Get domain bounds + xmin = Ref{Float64}(0.0) + xmax = Ref{Float64}(0.0) + ymin = Ref{Float64}(0.0) + ymax = Ref{Float64}(0.0) + domain_status = SparseIR.spir_kernel_domain(kernel, xmin, xmax, ymin, ymax) + @test domain_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # For LogisticKernel, we expect specific domain values + @test xmin[] ≈ -1.0 + @test xmax[] ≈ 1.0 + @test ymin[] ≈ -1.0 + @test ymax[] ≈ 1.0 + + SparseIR.spir_kernel_release(kernel) + end +end + +@testitem "FiniteTempBasis Constructor Tests" begin + using SparseIR + + # Helper function equivalent to C++ _spir_basis_new + function _spir_basis_new( + statistics::Integer, beta::Float64, omega_max::Float64, epsilon::Float64) + status = Ref{Int32}(0) + + # Create logistic kernel + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * omega_max, kernel_status) + if kernel_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || kernel == C_NULL + return C_NULL, kernel_status[] + end + + # Create SVE result + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + if sve_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || sve == C_NULL + SparseIR.spir_kernel_release(kernel) + return C_NULL, sve_status[] + end + + # Create basis + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new( + statistics, beta, omega_max, kernel, sve, basis_status) + if basis_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || basis == C_NULL + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + return C_NULL, basis_status[] + end + + # Clean up intermediate objects (like C++ version) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + + return basis, SparseIR.SPIR_COMPUTATION_SUCCESS + end + + # Test basis constructors (corresponds to cinterface_core.cxx TEST_CASE "FiniteTempBasis") + + function test_basis_constructor(statistics::Integer) + beta = 2.0 + wmax = 5.0 + epsilon = 1e-6 + + # Create basis using helper function (equivalent to C++ _spir_basis_new) + basis, basis_status = _spir_basis_new(statistics, beta, wmax, epsilon) + @test basis_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Check basis size + basis_size = Ref{Int32}(0) + size_result = SparseIR.spir_basis_get_size(basis, basis_size) + @test size_result == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis_size[] > 0 + + SparseIR.spir_basis_release(basis) + return basis_size[] + end + + function test_basis_constructor_with_sve(statistics::Integer, use_reg_bose=false) + beta = 2.0 + wmax = 5.0 + Lambda = 10.0 + epsilon = 1e-6 + + # Create kernel + kernel_status = Ref{Int32}(0) + kernel = if use_reg_bose + SparseIR.spir_reg_bose_kernel_new(Lambda, kernel_status) + else + SparseIR.spir_logistic_kernel_new(Lambda, kernel_status) + end + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test kernel != C_NULL + + # Create SVE result + sve_status = Ref{Int32}(0) + sve_result = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sve_result != C_NULL + + # Create basis with SVE + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new( + statistics, beta, wmax, kernel, sve_result, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Check statistics + stats = Ref{Int32}(0) + stats_status = SparseIR.spir_basis_get_stats(basis, stats) + @test stats_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test stats[] == statistics + + # Clean up + SparseIR.spir_kernel_release(kernel) + SparseIR.spir_sve_result_release(sve_result) + SparseIR.spir_basis_release(basis) + end + + @testset "FiniteTempBasis Constructor Fermionic" begin + test_basis_constructor(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "FiniteTempBasis Constructor Bosonic" begin + test_basis_constructor(SparseIR.SPIR_STATISTICS_BOSONIC) + end + + @testset "FiniteTempBasis Constructor with SVE Fermionic/LogisticKernel" begin + test_basis_constructor_with_sve(SparseIR.SPIR_STATISTICS_FERMIONIC, false) + end + + @testset "FiniteTempBasis Constructor with SVE Bosonic/LogisticKernel" begin + test_basis_constructor_with_sve(SparseIR.SPIR_STATISTICS_BOSONIC, false) + end + + @testset "FiniteTempBasis Constructor with SVE Bosonic/RegularizedBoseKernel" begin + test_basis_constructor_with_sve(SparseIR.SPIR_STATISTICS_BOSONIC, true) + end +end + +@testitem "FiniteTempBasis Basis Functions Tests" begin + using SparseIR + + # Helper function equivalent to C++ _spir_basis_new + function _spir_basis_new( + statistics::Integer, beta::Float64, omega_max::Float64, epsilon::Float64) + status = Ref{Int32}(0) + + # Create logistic kernel + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * omega_max, kernel_status) + if kernel_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || kernel == C_NULL + return C_NULL, kernel_status[] + end + + # Create SVE result + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + if sve_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || sve == C_NULL + SparseIR.spir_kernel_release(kernel) + return C_NULL, sve_status[] + end + + # Create basis + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new( + statistics, beta, omega_max, kernel, sve, basis_status) + if basis_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || basis == C_NULL + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + return C_NULL, basis_status[] + end + + # Clean up intermediate objects (like C++ version) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + + return basis, SparseIR.SPIR_COMPUTATION_SUCCESS + end + + # Test basis function evaluation (corresponds to cinterface_core.cxx TEST_CASE "FiniteTempBasis Basis Functions") + + function test_basis_functions(statistics::Integer) + beta = 2.0 + wmax = 5.0 + epsilon = 1e-6 + + # Create basis using helper function (equivalent to C++ _spir_basis_new) + basis, basis_status = _spir_basis_new(statistics, beta, wmax, epsilon) + @test basis_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Get basis size + basis_size = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + size = basis_size[] + + # Get u basis functions + u_status = Ref{Int32}(0) + u = SparseIR.spir_basis_get_u(basis, u_status) + @test u_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test u != C_NULL + + # Get uhat basis functions + uhat_status = Ref{Int32}(0) + uhat = SparseIR.spir_basis_get_uhat(basis, uhat_status) + @test uhat_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test uhat != C_NULL + + # Get v basis functions + v_status = Ref{Int32}(0) + v = SparseIR.spir_basis_get_v(basis, v_status) + @test v_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test v != C_NULL + + # Test single point evaluation for u basis + x = 0.5 # Test point for u basis (imaginary time) + out = Vector{Float64}(undef, size) + eval_status = SparseIR.spir_funcs_eval(u, x, out) + @test eval_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check that we got reasonable values + @test all(isfinite, out) + + # Test single point evaluation for v basis + y = 0.5 * wmax # Test point for v basis (real frequency) + eval_status = SparseIR.spir_funcs_eval(v, y, out) + @test eval_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test all(isfinite, out) + + # Test batch evaluation + num_points = 5 + xs = [0.2 * (i) for i in 1:num_points] # Points at 0.2, 0.4, 0.6, 0.8, 1.0 + batch_out = Vector{Float64}(undef, num_points * size) + + # Test row-major order for u basis + batch_status = SparseIR.spir_funcs_batch_eval( + u, SparseIR.SPIR_ORDER_ROW_MAJOR, num_points, xs, batch_out) + @test batch_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test all(isfinite, batch_out) + + # Test column-major order for u basis + batch_status = SparseIR.spir_funcs_batch_eval( + u, SparseIR.SPIR_ORDER_COLUMN_MAJOR, num_points, xs, batch_out) + @test batch_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test all(isfinite, batch_out) + + # Test row-major order for v basis + batch_status = SparseIR.spir_funcs_batch_eval( + v, SparseIR.SPIR_ORDER_ROW_MAJOR, num_points, xs, batch_out) + @test batch_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test all(isfinite, batch_out) + + # Test column-major order for v basis + batch_status = SparseIR.spir_funcs_batch_eval( + v, SparseIR.SPIR_ORDER_COLUMN_MAJOR, num_points, xs, batch_out) + @test batch_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test all(isfinite, batch_out) + + # Test error cases (corresponds to C++ error case testing) + # Test with null function pointer + eval_status = SparseIR.spir_funcs_eval(C_NULL, x, out) + @test eval_status != SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test with null output array - create a null pointer for testing + # Note: In Julia, we can't easily pass null for the output array as it would be unsafe + # This corresponds to spir_funcs_eval(u, x, nullptr) in C++ + # We skip this test for safety reasons in Julia + + # Test batch evaluation error cases + batch_status = SparseIR.spir_funcs_batch_eval( + C_NULL, SparseIR.SPIR_ORDER_ROW_MAJOR, num_points, xs, batch_out) + @test batch_status != SparseIR.SPIR_COMPUTATION_SUCCESS + + # Clean up + SparseIR.spir_funcs_release(u) + SparseIR.spir_funcs_release(v) + SparseIR.spir_funcs_release(uhat) + SparseIR.spir_basis_release(basis) + end + + @testset "Basis Functions Fermionic" begin + test_basis_functions(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "Basis Functions Bosonic" begin + test_basis_functions(SparseIR.SPIR_STATISTICS_BOSONIC) + end +end diff --git a/test/C_API/cinterface_dlr_tests.jl b/test/C_API/cinterface_dlr_tests.jl new file mode 100644 index 0000000..0014295 --- /dev/null +++ b/test/C_API/cinterface_dlr_tests.jl @@ -0,0 +1,123 @@ +# Tests corresponding to test/cpp/cinterface_dlr.cxx +# Comprehensive DLR (Discrete Lehmann Representation) functionality tests + +@testitem "DiscreteLehmannRepresentation" tags=[:cinterface] begin + using SparseIR + + # Helper function equivalent to C++ _spir_basis_new + function _spir_basis_new( + statistics::Integer, beta::Float64, omega_max::Float64, epsilon::Float64) + status = Ref{Int32}(0) + + # Create logistic kernel + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * omega_max, kernel_status) + if kernel_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || kernel == C_NULL + return C_NULL, kernel_status[] + end + + # Create SVE result + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + if sve_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || sve == C_NULL + SparseIR.spir_kernel_release(kernel) + return C_NULL, sve_status[] + end + + # Create basis + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new( + statistics, beta, omega_max, kernel, sve, basis_status) + if basis_status[] != SparseIR.SPIR_COMPUTATION_SUCCESS || basis == C_NULL + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + return C_NULL, basis_status[] + end + + # Clean up intermediate objects (like C++ version) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + + return basis, SparseIR.SPIR_COMPUTATION_SUCCESS + end + + # Template function equivalent for different statistics (corresponds to C++ template functions) + function test_finite_temp_basis_dlr(statistics::Integer) + beta = 10000.0 # Same as C++ version + wmax = 1.0 + epsilon = 1e-12 + + # Create IR basis using helper function (equivalent to C++ _spir_basis_new) + basis, basis_status = _spir_basis_new(statistics, beta, wmax, epsilon) + @test basis_status == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Get basis size + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + @test basis_size >= 0 + + # Get default poles (corresponds to C++ spir_basis_get_default_ws) + num_default_poles_ref = Ref{Int32}(0) + poles_status = SparseIR.spir_basis_get_n_default_ws(basis, num_default_poles_ref) + @test poles_status == SparseIR.SPIR_COMPUTATION_SUCCESS + num_default_poles = num_default_poles_ref[] + @test num_default_poles >= 0 + + default_poles = Vector{Float64}(undef, num_default_poles) + get_poles_status = SparseIR.spir_basis_get_default_ws(basis, default_poles) + @test get_poles_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # DLR constructor using default poles (corresponds to C++ spir_dlr_new) + dlr_status = Ref{Int32}(0) + dlr = SparseIR.spir_dlr_new(basis, dlr_status) + @test dlr_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr != C_NULL + + # DLR constructor using custom poles (corresponds to C++ spir_dlr_new_with_poles) + dlr_with_poles_status = Ref{Int32}(0) + dlr_with_poles = SparseIR.spir_dlr_new_with_poles( + basis, num_default_poles, default_poles, dlr_with_poles_status) + @test dlr_with_poles_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr_with_poles != C_NULL + + # Test number of poles consistency + num_poles_ref = Ref{Int32}(0) + npoles_status = SparseIR.spir_dlr_get_npoles(dlr, num_poles_ref) + @test npoles_status == SparseIR.SPIR_COMPUTATION_SUCCESS + num_poles = num_poles_ref[] + @test num_poles == num_default_poles + + num_poles_with_poles_ref = Ref{Int32}(0) + npoles_with_poles_status = SparseIR.spir_dlr_get_npoles( + dlr_with_poles, num_poles_with_poles_ref) + @test npoles_with_poles_status == SparseIR.SPIR_COMPUTATION_SUCCESS + num_poles_with_poles = num_poles_with_poles_ref[] + @test num_poles_with_poles == num_default_poles + + # Test poles reconstruction (corresponds to C++ strict comparison) + poles_reconst = Vector{Float64}(undef, num_poles) + reconst_status = SparseIR.spir_dlr_get_poles(dlr, poles_reconst) + @test reconst_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Strict numerical comparison (corresponds to C++ Approx comparison) + for i in 1:num_poles + @test poles_reconst[i]≈default_poles[i] atol=1e-14 + end + + # Clean up (corresponds to C++ cleanup) + SparseIR.spir_basis_release(dlr_with_poles) + SparseIR.spir_basis_release(dlr) + SparseIR.spir_basis_release(basis) + end + + @testset "DiscreteLehmannRepresentation Constructor Fermionic" begin + test_finite_temp_basis_dlr(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "DiscreteLehmannRepresentation Constructor Bosonic" begin + test_finite_temp_basis_dlr(SparseIR.SPIR_STATISTICS_BOSONIC) + end +end diff --git a/test/C_API/cinterface_integration_tests.jl b/test/C_API/cinterface_integration_tests.jl new file mode 100644 index 0000000..0b7aeb2 --- /dev/null +++ b/test/C_API/cinterface_integration_tests.jl @@ -0,0 +1,602 @@ +@testitem "cinterface_integration_tests.jl" tags=[:cinterface] begin + # Test file corresponding to test/cpp/cinterface_integration.cxx + + using SparseIR + using Test + using Random + + # Helper function corresponding to _get_dims in cinterface_integration.cxx + function _get_dims(target_dim_size::Integer, extra_dims::Vector{<:Integer}, + target_dim::Integer, ndim::Integer) + dims = Vector{Int32}(undef, ndim) + dims[target_dim + 1] = target_dim_size # Julia is 1-indexed + pos = 1 + for i in 1:ndim + if i == target_dim + 1 + continue + end + dims[i] = extra_dims[pos] + pos += 1 + end + return dims + end + + function generate_random_coeffs( + ::Type{<:Real}, random_value_real, random_value_imag, pole) + (2 * random_value_real - 1.0) * sqrt(abs(pole)) + end + + function generate_random_coeffs( + ::Type{<:Complex}, random_value_real, random_value_imag, pole) + real_part = (2 * random_value_real - 1.0) * sqrt(abs(pole)) + imag_part = (2 * random_value_imag - 1.0) * sqrt(abs(pole)) + return complex(real_part, imag_part) + end + + # Helper function corresponding to _spir_basis_new in cinterface_integration.cxx + function _spir_basis_new(statistics::Integer, beta::Float64, omega_max::Float64, + epsilon::Float64, status::Ref{Cint}) + # Create logistic kernel + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * omega_max, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test kernel != C_NULL + + # Create SVE result + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sve != C_NULL + + # Create basis + basis = SparseIR.spir_basis_new(statistics, beta, omega_max, kernel, sve, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Clean up intermediate objects (like C++ version) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + + return basis + end + + function getperm(N, src, dst) + perm = collect(1:N) + deleteat!(perm, src) + insert!(perm, dst, src) + return perm + end + + """ + movedim(arr::AbstractArray, src => dst) + + Move `arr`'s dimension at `src` to `dst` while keeping the order of the remaining + dimensions unchanged. + """ + function movedim(arr::AbstractArray{T,N}, src, dst) where {T,N} + src == dst && return arr + return permutedims(arr, getperm(N, src, dst)) + end + + function dlr_to_IR(dlr, order, ndim, dims, target_dim, + coeffs::AbstractArray{<:Real}, g_IR::AbstractArray{<:Real}) + SparseIR.spir_dlr2ir_dd(dlr, order, ndim, dims, target_dim, coeffs, g_IR) + end + + function dlr_to_IR(dlr, order, ndim, dims, target_dim, + coeffs::AbstractArray{<:Complex}, g_IR::AbstractArray{<:Complex}) + SparseIR.spir_dlr2ir_zz(dlr, order, ndim, dims, target_dim, coeffs, g_IR) + end + + function dlr_from_IR(dlr, order, ndim, dims, target_dim, g_IR::AbstractArray{<:Real}, + g_DLR_reconst::AbstractArray{<:Real}) + SparseIR.spir_ir2dlr_dd(dlr, order, ndim, dims, target_dim, g_IR, g_DLR_reconst) + end + + function dlr_from_IR( + dlr, order, ndim, dims, target_dim, g_IR::AbstractArray{<:Complex}, + g_DLR_reconst::AbstractArray{<:Complex}) + SparseIR.spir_ir2dlr_zz(dlr, order, ndim, dims, target_dim, g_IR, g_DLR_reconst) + end + + function compare_tensors_with_relative_error(a, b, tol) + diff = abs.(a - b) + ref = abs.(a) + max_diff = maximum(diff) + max_ref = maximum(ref) + + # Debug output like C++ version + if max_diff > tol * max_ref + println("max_diff: ", max_diff) + println("max_ref: ", max_ref) + println("tol: ", tol) + end + + return max_diff <= tol * max_ref + end + + function _transform_coefficients( + coeffs::AbstractArray{T,N}, basis_eval::AbstractMatrix{U}, + target_dim::Integer) where {T,U,N} + # Move the target dimension to the first position + coeffs_targetdim0 = movedim(coeffs, 1 + target_dim, 1) + + # Calculate the size of the extra dimensions + extra_size = prod(size(coeffs_targetdim0)[2:end]) + + # Create result tensor with correct dimensions + dims = collect(size(coeffs_targetdim0)) + dims[1] = size(basis_eval, 1) + + # Initialize the result + PromotedType = promote_type(T, U) + result = Array{PromotedType,N}(undef, dims...) + + # Map tensors to matrices for multiplication + coeffs_mat = reshape(coeffs_targetdim0, size(coeffs_targetdim0, 1), extra_size) + result_mat = reshape(result, size(basis_eval, 1), extra_size) + + # Perform the matrix multiplication + result_mat .= basis_eval * coeffs_mat + + # Move dimension back to original order + return movedim(result, 1, 1 + target_dim) + end + + function _evaluate_basis_functions(::Type{T}, u, x_values) where {T} + status = Ref{Cint}(-100) + funcs_size_ref = Ref{Cint}(0) + status[] = SparseIR.spir_funcs_get_size(u, funcs_size_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + funcs_size = funcs_size_ref[] + + u_eval_mat = Matrix{T}(undef, length(x_values), funcs_size) + for i in eachindex(x_values) + u_eval = Vector{T}(undef, funcs_size) + status[] = SparseIR.spir_funcs_eval(u, x_values[i], u_eval) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + u_eval_mat[i, :] .= u_eval + end + return u_eval_mat + end + + function _evaluate_gtau(coeffs::AbstractArray{T,N}, u, target_dim, x_values) where {T,N} + u_eval_mat = _evaluate_basis_functions(Float64, u, x_values) + return _transform_coefficients(coeffs, u_eval_mat, target_dim) + end + + function _evaluate_matsubara_basis_functions(uhat, matsubara_indices) + status = Ref{Cint}(-100) + funcs_size_ref = Ref{Cint}(0) + status[] = SparseIR.spir_funcs_get_size(uhat, funcs_size_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + funcs_size = funcs_size_ref[] + + uhat_eval_mat = Matrix{ComplexF64}(undef, length(matsubara_indices), funcs_size) + freq_indices = Int64.(matsubara_indices) + order = SparseIR.SPIR_ORDER_COLUMN_MAJOR + status[] = SparseIR.spir_funcs_batch_eval_matsu( + uhat, order, length(matsubara_indices), freq_indices, uhat_eval_mat) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + return uhat_eval_mat + end + + function _evaluate_giw(coeffs, uhat, target_dim, matsubara_indices) + uhat_eval_mat = _evaluate_matsubara_basis_functions(uhat, matsubara_indices) + result = _transform_coefficients(coeffs, uhat_eval_mat, target_dim) + return result + end + + function _tau_sampling_evaluate(sampling, order, ndim, dims, target_dim, + gIR::AbstractArray{<:Real}, gtau::AbstractArray{<:Real}) + status = SparseIR.spir_sampling_eval_dd( + sampling, order, ndim, dims, target_dim, gIR, gtau) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + function _tau_sampling_evaluate(sampling, order, ndim, dims, target_dim, + gIR::AbstractArray{<:Complex}, gtau::AbstractArray{<:Complex}) + status = SparseIR.spir_sampling_eval_zz( + sampling, order, ndim, dims, target_dim, gIR, gtau) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + function _tau_sampling_fit(sampling, order, ndim, dims, target_dim, + gtau::AbstractArray{<:Real}, gIR::AbstractArray{<:Real}) + status = SparseIR.spir_sampling_fit_dd( + sampling, order, ndim, dims, target_dim, gtau, gIR) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + function _tau_sampling_fit(sampling, order, ndim, dims, target_dim, + gtau::AbstractArray{<:Complex}, gIR::AbstractArray{<:Complex}) + status = SparseIR.spir_sampling_fit_zz( + sampling, order, ndim, dims, target_dim, gtau, gIR) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + function _matsubara_sampling_evaluate(sampling, order, ndim, dims, target_dim, + gIR::AbstractArray{<:Real}, giw::AbstractArray{<:Complex}) + status = SparseIR.spir_sampling_eval_dz( + sampling, order, ndim, dims, target_dim, gIR, giw) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + function _matsubara_sampling_evaluate(sampling, order, ndim, dims, target_dim, + gIR::AbstractArray{<:Complex}, giw::AbstractArray{<:Complex}) + status = SparseIR.spir_sampling_eval_zz( + sampling, order, ndim, dims, target_dim, gIR, giw) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + return status + end + + # Main integration test function + function integration_test(::Type{T}, beta, wmax, epsilon, extra_dims, + target_dim, order, tol, positive_only) where {T} + # positive_only is not supported for complex numbers + @test !(T <: Complex && positive_only) + + ndim = 1 + length(extra_dims) + @test ndim == 1 + length(extra_dims) + + # Verify that the order parameter is consistent + if order == SparseIR.SPIR_ORDER_COLUMN_MAJOR + # Column major order + else + # Row major order + end + + stat = SparseIR.SPIR_STATISTICS_BOSONIC + status = Ref{Cint}(-100) + + # IR basis + basis = _spir_basis_new(stat, beta, wmax, epsilon, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + basis_size_ref = Ref{Cint}(-100) + status[] = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + # Tau Sampling + println("Tau sampling") + num_tau_points_ref = Ref{Cint}(-100) + status[] = SparseIR.spir_basis_get_n_default_taus(basis, num_tau_points_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + num_tau_points = num_tau_points_ref[] + @test num_tau_points > 0 + + tau_points_org = Vector{Float64}(undef, num_tau_points) + status[] = SparseIR.spir_basis_get_default_taus(basis, tau_points_org) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + tau_sampling_status = Ref{Cint}(-100) + tau_sampling = SparseIR.spir_tau_sampling_new( + basis, num_tau_points, tau_points_org, tau_sampling_status) + @test tau_sampling_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test tau_sampling != C_NULL + + status[] = SparseIR.spir_sampling_get_npoints(tau_sampling, num_tau_points_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + num_tau_points = num_tau_points_ref[] + tau_points = Vector{Float64}(undef, num_tau_points) + status[] = SparseIR.spir_sampling_get_taus(tau_sampling, tau_points) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test num_tau_points >= basis_size + + # Compare tau_points and tau_points_org + for i in 1:num_tau_points + @test tau_points[i] ≈ tau_points_org[i] + end + + # Matsubara Sampling + println("Matsubara sampling") + num_matsubara_points_org_ref = Ref{Cint}(0) + status[] = SparseIR.spir_basis_get_n_default_matsus( + basis, positive_only, num_matsubara_points_org_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + num_matsubara_points_org = num_matsubara_points_org_ref[] + @test num_matsubara_points_org > 0 + + matsubara_points_org = Vector{Int64}(undef, num_matsubara_points_org) + status[] = SparseIR.spir_basis_get_default_matsus( + basis, positive_only, matsubara_points_org) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + matsubara_sampling_status = Ref{Cint}(-100) + matsubara_sampling = SparseIR.spir_matsu_sampling_new( + basis, positive_only, num_matsubara_points_org, + matsubara_points_org, matsubara_sampling_status) + @test matsubara_sampling_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test matsubara_sampling != C_NULL + + if positive_only + @test num_matsubara_points_org >= basis_size ÷ 2 + else + @test num_matsubara_points_org >= basis_size + end + + num_matsubara_points_ref = Ref{Cint}(0) + status[] = SparseIR.spir_sampling_get_npoints( + matsubara_sampling, num_matsubara_points_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + num_matsubara_points = num_matsubara_points_ref[] + matsubara_points = Vector{Int64}(undef, num_matsubara_points) + status[] = SparseIR.spir_sampling_get_matsus(matsubara_sampling, matsubara_points) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Compare matsubara_points and matsubara_points_org + for i in 1:num_matsubara_points + @test matsubara_points[i] == matsubara_points_org[i] + end + + # DLR + println("DLR") + dlr_status = Ref{Cint}(-100) + dlr = SparseIR.spir_dlr_new(basis, dlr_status) + @test dlr_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr != C_NULL + + npoles_ref = Ref{Cint}(-100) + status[] = SparseIR.spir_dlr_get_npoles(dlr, npoles_ref) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + npoles = npoles_ref[] + @test npoles >= basis_size + + poles = Vector{Float64}(undef, npoles) + status[] = SparseIR.spir_dlr_get_poles(dlr, poles) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Calculate total size of extra dimensions + extra_size = prod(extra_dims) + + # Generate random DLR coefficients + coeffs_targetdim0 = Array{T,ndim}(undef, npoles, extra_dims...) + + coeffs_2d = reshape(coeffs_targetdim0, Int64(npoles), Int64(extra_size)) + Random.seed!(982743) # Same seed as C++ version + for i in 1:npoles + for j in 1:extra_size + coeffs_2d[i, j] = generate_random_coeffs(T, rand(), rand(), poles[i]) + end + end + #coeffs_targetdim0 .= 0.0 + #coeffs_targetdim0[npoles ÷ 2] = 1.0 + #coeffs_targetdim0[npoles ÷ 2 + 1] = 1.0 + + # DLR sampling objects (MISSING in original Julia code) + tau_sampling_dlr_status = Ref{Cint}(-100) + tau_sampling_dlr = SparseIR.spir_tau_sampling_new( + dlr, num_tau_points, tau_points_org, tau_sampling_dlr_status) + @test tau_sampling_dlr_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test tau_sampling_dlr != C_NULL + + dlr_uhat2_status = Ref{Cint}(-100) + dlr_uhat2 = SparseIR.spir_basis_get_uhat(dlr, dlr_uhat2_status) + @test dlr_uhat2_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr_uhat2 != C_NULL + @test SparseIR.spir_funcs_is_assigned(dlr_uhat2) == 1 + + matsubara_sampling_dlr_status = Ref{Cint}(-100) + matsubara_sampling_dlr = SparseIR.spir_matsu_sampling_new( + dlr, positive_only, num_matsubara_points_org, + matsubara_points_org, matsubara_sampling_dlr_status) + @test matsubara_sampling_dlr_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test matsubara_sampling_dlr != C_NULL + + # Move the axis for the poles from the first to the target dimension + coeffs = movedim(coeffs_targetdim0, 1, 1 + target_dim) + + # Convert DLR coefficients to IR coefficients + g_IR = Array{T,ndim}(undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + status[] = dlr_to_IR( + dlr, order, ndim, _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, coeffs, g_IR) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Convert IR coefficients back to DLR coefficients (this should reconstruct the original coeffs) + # Note: C++ version has a bug here - it creates g_DLR_reconst with basis_size but calls with npoles dims + # We follow the C++ version exactly to match behavior + g_DLR_reconst = Array{T,ndim}( + undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + status[] = dlr_from_IR( + dlr, order, ndim, _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, g_IR, g_DLR_reconst) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # From IR to DLR + g_dlr = Array{T,ndim}(undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + status[] = dlr_from_IR( + dlr, order, ndim, _get_dims(basis_size, extra_dims, target_dim, ndim), + target_dim, g_IR, g_dlr) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # DLR basis functions + dlr_u_status = Ref{Cint}(-100) + dlr_u = SparseIR.spir_basis_get_u(dlr, dlr_u_status) + @test dlr_u_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr_u != C_NULL + + dlr_uhat_status = Ref{Cint}(-100) + dlr_uhat = SparseIR.spir_basis_get_uhat(dlr, dlr_uhat_status) + @test dlr_uhat_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test dlr_uhat != C_NULL + + # IR basis functions + ir_u_status = Ref{Cint}(-100) + ir_u = SparseIR.spir_basis_get_u(basis, ir_u_status) + @test ir_u_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test ir_u != C_NULL + + ir_uhat_status = Ref{Cint}(-100) + ir_uhat = SparseIR.spir_basis_get_uhat(basis, ir_uhat_status) + @test ir_uhat_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test ir_uhat != C_NULL + + # Compare the Greens function at all tau points between IR and DLR + gtau_from_IR = _evaluate_gtau(g_IR, ir_u, target_dim, tau_points) + gtau_from_DLR = _evaluate_gtau(coeffs, dlr_u, target_dim, tau_points) + gtau_from_DLR_reconst = _evaluate_gtau(g_DLR_reconst, dlr_u, target_dim, tau_points) + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR, tol) + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR_reconst, tol) + + # Use sampling to evaluate the Greens function at all tau points between IR and DLR (MISSING in original Julia code) + gtau_from_DLR_sampling = similar(gtau_from_DLR) + if T <: Real + status[] = SparseIR.spir_sampling_eval_dd( + tau_sampling_dlr, order, ndim, + _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, coeffs, gtau_from_DLR_sampling) + elseif T <: Complex + status[] = SparseIR.spir_sampling_eval_zz( + tau_sampling_dlr, order, ndim, + _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, coeffs, gtau_from_DLR_sampling) + end + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR_sampling, tol) + + # Compare the Greens function at all Matsubara frequencies between IR and DLR + giw_from_IR = _evaluate_giw(g_IR, ir_uhat, target_dim, matsubara_points) + giw_from_DLR = _evaluate_giw(coeffs, dlr_uhat, target_dim, matsubara_points) + @test compare_tensors_with_relative_error(giw_from_IR, giw_from_DLR, tol) + + # Use sampling to evaluate the Greens function at all Matsubara frequencies between IR and DLR (MISSING in original Julia code) + giw_from_DLR_sampling = similar(giw_from_DLR) + if T <: Real + status[] = SparseIR.spir_sampling_eval_dz( + matsubara_sampling_dlr, order, ndim, + _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, coeffs, giw_from_DLR_sampling) + elseif T <: Complex + status[] = SparseIR.spir_sampling_eval_zz( + matsubara_sampling_dlr, order, ndim, + _get_dims(npoles, extra_dims, target_dim, ndim), + target_dim, coeffs, giw_from_DLR_sampling) + end + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test compare_tensors_with_relative_error(giw_from_IR, giw_from_DLR_sampling, tol) + + dims_matsubara = _get_dims(num_matsubara_points, extra_dims, target_dim, ndim) + dims_IR = _get_dims(basis_size, extra_dims, target_dim, ndim) + dims_tau = _get_dims(num_tau_points, extra_dims, target_dim, ndim) + + gIR = Array{T,ndim}(undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + gIR2 = Array{T,ndim}(undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + gtau = Array{T,ndim}( + undef, _get_dims(num_tau_points, extra_dims, target_dim, ndim)...) + giw_reconst = Array{ComplexF64,ndim}( + undef, _get_dims(num_matsubara_points, extra_dims, target_dim, ndim)...) + + # Matsubara -> IR + begin + gIR_work = Array{ComplexF64,ndim}( + undef, _get_dims(basis_size, extra_dims, target_dim, ndim)...) + status[] = SparseIR.spir_sampling_fit_zz( + matsubara_sampling, order, ndim, dims_matsubara, target_dim, giw_from_DLR, gIR_work + ) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + if T <: Real + gIR .= real(gIR_work) + else + gIR .= gIR_work + end + end + + # IR -> tau + status[] = _tau_sampling_evaluate( + tau_sampling, order, ndim, dims_IR, target_dim, gIR, gtau) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # tau -> IR + status[] = _tau_sampling_fit( + tau_sampling, order, ndim, dims_tau, target_dim, gtau, gIR2) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # IR -> Matsubara + status[] = _matsubara_sampling_evaluate( + matsubara_sampling, order, ndim, dims_IR, target_dim, gIR2, giw_reconst) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Final comparison test (MISSING in original Julia code) + giw_from_IR_reconst = _evaluate_giw(gIR2, ir_uhat, target_dim, matsubara_points) + @test compare_tensors_with_relative_error(giw_from_DLR, giw_from_IR_reconst, tol) + + # Memory cleanup (MISSING in original Julia code) + SparseIR.spir_basis_release(basis) + SparseIR.spir_basis_release(dlr) + SparseIR.spir_funcs_release(dlr_u) + SparseIR.spir_funcs_release(ir_u) + SparseIR.spir_sampling_release(tau_sampling) + SparseIR.spir_sampling_release(tau_sampling_dlr) + SparseIR.spir_sampling_release(matsubara_sampling) + SparseIR.spir_sampling_release(matsubara_sampling_dlr) + end + + # Test parameters + beta = 1e+4 + wmax = 2.0 + epsilon = 1e-10 + tol = 10 * epsilon + + # Run tests for different configurations like C++ version + for positive_only in [false, true] + println("positive_only = ", positive_only) + + # Test 1: Simple 1D case + begin + extra_dims = Int[] + println("Integration test for bosonic LogisticKernel") + integration_test(Float64, beta, wmax, epsilon, extra_dims, 0, + SparseIR.SPIR_ORDER_COLUMN_MAJOR, tol, positive_only) + + if !positive_only + integration_test(ComplexF64, beta, wmax, epsilon, extra_dims, 0, + SparseIR.SPIR_ORDER_COLUMN_MAJOR, tol, positive_only) + end + end + + # Test 2: ColMajor, target_dim = 0 + begin + target_dim = 0 + extra_dims = Int[] + println("Integration test for bosonic LogisticKernel, ColMajor, target_dim = ", + target_dim) + integration_test(Float64, beta, wmax, epsilon, extra_dims, target_dim, + SparseIR.SPIR_ORDER_COLUMN_MAJOR, tol, positive_only) + if !positive_only + integration_test(ComplexF64, beta, wmax, epsilon, extra_dims, target_dim, + SparseIR.SPIR_ORDER_COLUMN_MAJOR, tol, positive_only) + end + end + + # Test 3: RowMajor, target_dim = 0 + begin + target_dim = 0 + extra_dims = Int[] + println("Integration test for bosonic LogisticKernel, RowMajor, target_dim = ", + target_dim) + integration_test(Float64, beta, wmax, epsilon, extra_dims, target_dim, + SparseIR.SPIR_ORDER_ROW_MAJOR, tol, positive_only) + if !positive_only + integration_test(ComplexF64, beta, wmax, epsilon, extra_dims, target_dim, + SparseIR.SPIR_ORDER_ROW_MAJOR, tol, positive_only) + end + end + + # Test 4: Multi-dimensional cases with extra dims = [2,3,4] + for target_dim in 0:3 + extra_dims = [2, 3, 4] + println("Integration test for bosonic LogisticKernel, ColMajor, target_dim = ", + target_dim) + integration_test(Float64, beta, wmax, epsilon, extra_dims, target_dim, + SparseIR.SPIR_ORDER_COLUMN_MAJOR, tol, positive_only) + end + end +end diff --git a/test/C_API/cinterface_sampling_tests.jl b/test/C_API/cinterface_sampling_tests.jl new file mode 100644 index 0000000..e1fa7da --- /dev/null +++ b/test/C_API/cinterface_sampling_tests.jl @@ -0,0 +1,735 @@ +# Tests corresponding to test/cpp/cinterface_sampling.cxx +# Comprehensive sampling functionality tests including TauSampling and MatsubaraSampling + +@testitem "TauSampling" tags=[:cinterface] begin + using SparseIR + + # Helper function to create tau sampling (corresponds to C++ create_tau_sampling) + function create_tau_sampling(basis::Ptr{SparseIR.spir_basis}) + status = Ref{Int32}(0) + + n_tau_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_basis_get_n_default_taus(basis, n_tau_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_tau_points = n_tau_points_ref[] + + tau_points = Vector{Float64}(undef, n_tau_points) + get_points_status = SparseIR.spir_basis_get_default_taus(basis, tau_points) + @test get_points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + sampling = SparseIR.spir_tau_sampling_new(basis, n_tau_points, tau_points, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sampling != C_NULL + + return sampling + end + + # Test tau sampling constructor (corresponds to C++ test_tau_sampling template) + function test_tau_sampling(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-15 + + # Create basis using kernel and SVE (equivalent to _spir_basis_new) + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test basis != C_NULL + + # Get tau points + n_tau_points_ref = Ref{Int32}(0) + status = SparseIR.spir_basis_get_n_default_taus(basis, n_tau_points_ref) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_tau_points = n_tau_points_ref[] + @test n_tau_points > 0 + + tau_points_org = Vector{Float64}(undef, n_tau_points) + tau_status = SparseIR.spir_basis_get_default_taus(basis, tau_points_org) + @test tau_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling_status = Ref{Int32}(0) + sampling = SparseIR.spir_tau_sampling_new( + basis, n_tau_points, tau_points_org, sampling_status) + @test sampling_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sampling != C_NULL + + # Test getting number of sampling points + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + @test n_points > 0 + + # Test getting sampling points + tau_points = Vector{Float64}(undef, n_points) + get_tau_status = SparseIR.spir_sampling_get_taus(sampling, tau_points) + @test get_tau_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Compare tau_points and tau_points_org (corresponds to C++ comparison) + for i in 1:n_points + @test tau_points[i]≈tau_points_org[i] atol=1e-14 + end + + # Test that matsubara points are not supported for tau sampling + matsubara_points = Vector{Int64}(undef, n_points) + matsubara_status = SparseIR.spir_sampling_get_matsus(sampling, matsubara_points) + @test matsubara_status == SparseIR.SPIR_NOT_SUPPORTED + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test 1D evaluation (corresponds to C++ test_tau_sampling_evaluation_1d_column_major) + function test_tau_sampling_evaluation_1d(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up parameters for evaluation + ndim = 1 + dims = Int32[basis_size] + target_dim = 0 + + # Create test coefficients + coeffs = rand(Float64, basis_size) .- 0.5 + + # Test evaluation + evaluate_output = Vector{Float64}(undef, n_points) + evaluate_status = SparseIR.spir_sampling_eval_dd( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, + dims, target_dim, coeffs, evaluate_output) + @test evaluate_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test fitting + fit_output = Vector{Float64}(undef, basis_size) + fit_status = SparseIR.spir_sampling_fit_dd( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, dims, + target_dim, evaluate_output, fit_output) + @test fit_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check round-trip accuracy + for i in 1:basis_size + @test fit_output[i]≈coeffs[i] atol=1e-10 + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test 4D evaluation with row-major layout (corresponds to C++ test_tau_sampling_evaluation_4d_row_major) + function test_tau_sampling_evaluation_4d_row_major(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up 4D tensor dimensions + d1, d2, d3 = 2, 3, 4 + ndim = 4 + + # Test evaluation and fitting along each dimension + for dim in 0:3 + # Create dimension arrays for different target dimensions + if dim == 0 + dims = Int32[basis_size, d1, d2, d3] + output_dims = Int32[n_points, d1, d2, d3] + elseif dim == 1 + dims = Int32[d1, basis_size, d2, d3] + output_dims = Int32[d1, n_points, d2, d3] + elseif dim == 2 + dims = Int32[d1, d2, basis_size, d3] + output_dims = Int32[d1, d2, n_points, d3] + else # dim == 3 + dims = Int32[d1, d2, d3, basis_size] + output_dims = Int32[d1, d2, d3, n_points] + end + + target_dim = dim + total_size = prod(dims) + output_total_size = prod(output_dims) + + # Create random test data (row-major layout) + coeffs = rand(Float64, total_size) .- 0.5 + + # Test evaluation + evaluate_output = Vector{Float64}(undef, output_total_size) + evaluate_status = SparseIR.spir_sampling_eval_dd( + sampling, SparseIR.SPIR_ORDER_ROW_MAJOR, ndim, + dims, target_dim, coeffs, evaluate_output) + @test evaluate_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test fitting + fit_output = Vector{Float64}(undef, total_size) + fit_status = SparseIR.spir_sampling_fit_dd( + sampling, SparseIR.SPIR_ORDER_ROW_MAJOR, ndim, output_dims, + target_dim, evaluate_output, fit_output) + @test fit_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check round-trip accuracy + for i in 1:total_size + @test fit_output[i]≈coeffs[i] atol=1e-10 + end + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test 4D evaluation with column-major layout (corresponds to C++ test_tau_sampling_evaluation_4d_column_major) + function test_tau_sampling_evaluation_4d_column_major(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up 4D tensor dimensions + d1, d2, d3 = 2, 3, 4 + ndim = 4 + + # Test evaluation and fitting along each dimension + for dim in 0:3 + # Create dimension arrays for different target dimensions + if dim == 0 + dims = Int32[basis_size, d1, d2, d3] + output_dims = Int32[n_points, d1, d2, d3] + elseif dim == 1 + dims = Int32[d1, basis_size, d2, d3] + output_dims = Int32[d1, n_points, d2, d3] + elseif dim == 2 + dims = Int32[d1, d2, basis_size, d3] + output_dims = Int32[d1, d2, n_points, d3] + else # dim == 3 + dims = Int32[d1, d2, d3, basis_size] + output_dims = Int32[d1, d2, d3, n_points] + end + + target_dim = dim + total_size = prod(dims) + output_total_size = prod(output_dims) + + # Create random test data (column-major layout) + coeffs = rand(Float64, total_size) .- 0.5 + + # Test evaluation + evaluate_output = Vector{Float64}(undef, output_total_size) + evaluate_status = SparseIR.spir_sampling_eval_dd( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, + dims, target_dim, coeffs, evaluate_output) + @test evaluate_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test fitting + fit_output = Vector{Float64}(undef, total_size) + fit_status = SparseIR.spir_sampling_fit_dd( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, output_dims, + target_dim, evaluate_output, fit_output) + @test fit_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check round-trip accuracy + for i in 1:total_size + @test fit_output[i]≈coeffs[i] atol=1e-10 + end + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test 4D evaluation with complex data and row-major layout (corresponds to C++ test_tau_sampling_evaluation_4d_row_major_complex) + function test_tau_sampling_evaluation_4d_row_major_complex(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up 4D tensor dimensions + d1, d2, d3 = 2, 3, 4 + ndim = 4 + + # Test evaluation and fitting along each dimension + for dim in 0:3 + # Create dimension arrays for different target dimensions + if dim == 0 + dims = Int32[basis_size, d1, d2, d3] + output_dims = Int32[n_points, d1, d2, d3] + elseif dim == 1 + dims = Int32[d1, basis_size, d2, d3] + output_dims = Int32[d1, n_points, d2, d3] + elseif dim == 2 + dims = Int32[d1, d2, basis_size, d3] + output_dims = Int32[d1, d2, n_points, d3] + else # dim == 3 + dims = Int32[d1, d2, d3, basis_size] + output_dims = Int32[d1, d2, d3, n_points] + end + + target_dim = dim + total_size = prod(dims) + output_total_size = prod(output_dims) + + # Create random complex test data (row-major layout) + coeffs_real = rand(Float64, total_size) .- 0.5 + coeffs_imag = rand(Float64, total_size) .- 0.5 + coeffs = [ComplexF64(coeffs_real[i], coeffs_imag[i]) for i in 1:total_size] + + # Test evaluation + evaluate_output = Vector{ComplexF64}(undef, output_total_size) + evaluate_status = SparseIR.spir_sampling_eval_zz( + sampling, SparseIR.SPIR_ORDER_ROW_MAJOR, ndim, + dims, target_dim, coeffs, evaluate_output) + @test evaluate_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test fitting + fit_output = Vector{ComplexF64}(undef, total_size) + fit_status = SparseIR.spir_sampling_fit_zz( + sampling, SparseIR.SPIR_ORDER_ROW_MAJOR, ndim, output_dims, + target_dim, evaluate_output, fit_output) + @test fit_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check round-trip accuracy + for i in 1:total_size + @test real(fit_output[i])≈real(coeffs[i]) atol=1e-10 + @test imag(fit_output[i])≈imag(coeffs[i]) atol=1e-10 + end + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test 4D evaluation with complex data and column-major layout (corresponds to C++ test_tau_sampling_evaluation_4d_column_major_complex) + function test_tau_sampling_evaluation_4d_column_major_complex(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up 4D tensor dimensions + d1, d2, d3 = 2, 3, 4 + ndim = 4 + + # Test evaluation and fitting along each dimension + for dim in 0:3 + # Create dimension arrays for different target dimensions + if dim == 0 + dims = Int32[basis_size, d1, d2, d3] + output_dims = Int32[n_points, d1, d2, d3] + elseif dim == 1 + dims = Int32[d1, basis_size, d2, d3] + output_dims = Int32[d1, n_points, d2, d3] + elseif dim == 2 + dims = Int32[d1, d2, basis_size, d3] + output_dims = Int32[d1, d2, n_points, d3] + else # dim == 3 + dims = Int32[d1, d2, d3, basis_size] + output_dims = Int32[d1, d2, d3, n_points] + end + + target_dim = dim + total_size = prod(dims) + output_total_size = prod(output_dims) + + # Create random complex test data (column-major layout) + coeffs_real = rand(Float64, total_size) .- 0.5 + coeffs_imag = rand(Float64, total_size) .- 0.5 + coeffs = [ComplexF64(coeffs_real[i], coeffs_imag[i]) for i in 1:total_size] + + # Test evaluation + evaluate_output = Vector{ComplexF64}(undef, output_total_size) + evaluate_status = SparseIR.spir_sampling_eval_zz( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, + dims, target_dim, coeffs, evaluate_output) + @test evaluate_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test fitting + fit_output = Vector{ComplexF64}(undef, total_size) + fit_status = SparseIR.spir_sampling_fit_zz( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, output_dims, + target_dim, evaluate_output, fit_output) + @test fit_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Check round-trip accuracy + for i in 1:total_size + @test real(fit_output[i])≈real(coeffs[i]) atol=1e-10 + @test imag(fit_output[i])≈imag(coeffs[i]) atol=1e-10 + end + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + # Test error status handling (corresponds to C++ TauSampling Error Status section) + function test_tau_sampling_error_status(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Create sampling + sampling = create_tau_sampling(basis) + + # Get basis and sampling sizes + basis_size_ref = Ref{Int32}(0) + size_status = SparseIR.spir_basis_get_size(basis, basis_size_ref) + @test size_status == SparseIR.SPIR_COMPUTATION_SUCCESS + basis_size = basis_size_ref[] + + n_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + + # Set up 4D tensor dimensions + d1, d2, d3 = 2, 3, 4 + ndim = 4 + total_size = basis_size * d1 * d2 * d3 + + # Create test data + coeffs = rand(Float64, total_size) .- 0.5 + output_double = Vector{Float64}(undef, total_size) + output_complex = Vector{ComplexF64}(undef, total_size) + fit_output_double = Vector{Float64}(undef, total_size) + fit_output_complex = Vector{ComplexF64}(undef, total_size) + + # Test dimension mismatch errors for different target dimensions + dims1 = Int32[basis_size, d1, d2, d3] + + for dim in 1:3 # Skip dim=0 as it should work + target_dim = dim + + # Test dimension mismatch for evaluation + status_dimension_mismatch = SparseIR.spir_sampling_eval_dd( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, + dims1, target_dim, coeffs, output_double) + @test status_dimension_mismatch == SparseIR.SPIR_INPUT_DIMENSION_MISMATCH + + # Test dimension mismatch for fitting + fit_status_dimension_mismatch = SparseIR.spir_sampling_fit_zz( + sampling, SparseIR.SPIR_ORDER_COLUMN_MAJOR, ndim, dims1, + target_dim, output_complex, fit_output_complex) + @test fit_status_dimension_mismatch == SparseIR.SPIR_INPUT_DIMENSION_MISMATCH + end + + # Clean up + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + @testset "TauSampling Constructor (fermionic)" begin + test_tau_sampling(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Constructor (bosonic)" begin + test_tau_sampling(SparseIR.SPIR_STATISTICS_BOSONIC) + end + + @testset "TauSampling Evaluation 1-dimensional input COLUMN-MAJOR" begin + test_tau_sampling_evaluation_1d(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Evaluation 4-dimensional input ROW-MAJOR" begin + test_tau_sampling_evaluation_4d_row_major(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Evaluation 4-dimensional input COLUMN-MAJOR" begin + test_tau_sampling_evaluation_4d_column_major(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Evaluation 4-dimensional complex input/output ROW-MAJOR" begin + test_tau_sampling_evaluation_4d_row_major_complex(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Evaluation 4-dimensional complex input/output COLUMN-MAJOR" begin + test_tau_sampling_evaluation_4d_column_major_complex(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "TauSampling Error Status" begin + test_tau_sampling_error_status(SparseIR.SPIR_STATISTICS_FERMIONIC) + end +end + +@testitem "MatsubaraSampling" begin + using SparseIR + + # Helper function to create matsubara sampling (corresponds to C++ create_matsubara_sampling) + function create_matsubara_sampling(basis::Ptr{SparseIR.spir_basis}, positive_only::Bool) + status = Ref{Int32}(0) + + n_matsubara_points_ref = Ref{Int32}(0) + points_status = SparseIR.spir_basis_get_n_default_matsus( + basis, positive_only, n_matsubara_points_ref) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_matsubara_points = n_matsubara_points_ref[] + + smpl_points = Vector{Int64}(undef, n_matsubara_points) + get_points_status = SparseIR.spir_basis_get_default_matsus( + basis, positive_only, smpl_points) + @test get_points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + sampling = SparseIR.spir_matsu_sampling_new( + basis, positive_only, n_matsubara_points, smpl_points, status) + @test status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sampling != C_NULL + + return sampling + end + + # Test matsubara sampling constructor (corresponds to C++ test_matsubara_sampling_constructor) + function test_matsubara_sampling_constructor(statistics::Integer) + beta = 1.0 + wmax = 10.0 + epsilon = 1e-10 + + # Create basis + kernel_status = Ref{Int32}(0) + kernel = SparseIR.spir_logistic_kernel_new(beta * wmax, kernel_status) + @test kernel_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + sve_status = Ref{Int32}(0) + sve = SparseIR.spir_sve_result_new(kernel, epsilon, sve_status) + @test sve_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + basis_status = Ref{Int32}(0) + basis = SparseIR.spir_basis_new(statistics, beta, wmax, kernel, sve, basis_status) + @test basis_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + + # Test with positive_only = false + n_points_org_ref = Ref{Int32}(0) + status = SparseIR.spir_basis_get_n_default_matsus(basis, false, n_points_org_ref) + @test status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points_org = n_points_org_ref[] + @test n_points_org > 0 + + smpl_points_org = Vector{Int64}(undef, n_points_org) + points_status = SparseIR.spir_basis_get_default_matsus( + basis, false, smpl_points_org) + @test points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + sampling_status = Ref{Int32}(0) + sampling = SparseIR.spir_matsu_sampling_new( + basis, false, n_points_org, smpl_points_org, sampling_status) + @test sampling_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sampling != C_NULL + + # Test with positive_only = true + n_points_positive_only_org_ref = Ref{Int32}(0) + positive_status = SparseIR.spir_basis_get_n_default_matsus( + basis, true, n_points_positive_only_org_ref) + @test positive_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points_positive_only_org = n_points_positive_only_org_ref[] + @test n_points_positive_only_org > 0 + + smpl_points_positive_only_org = Vector{Int64}(undef, n_points_positive_only_org) + positive_points_status = SparseIR.spir_basis_get_default_matsus( + basis, true, smpl_points_positive_only_org) + @test positive_points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + + sampling_positive_status = Ref{Int32}(0) + sampling_positive_only = SparseIR.spir_matsu_sampling_new( + basis, true, n_points_positive_only_org, + smpl_points_positive_only_org, sampling_positive_status) + @test sampling_positive_status[] == SparseIR.SPIR_COMPUTATION_SUCCESS + @test sampling_positive_only != C_NULL + + # Test getting number of points + n_points_ref = Ref{Int32}(0) + get_points_status = SparseIR.spir_sampling_get_npoints(sampling, n_points_ref) + @test get_points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points = n_points_ref[] + @test n_points > 0 + + n_points_positive_only_ref = Ref{Int32}(0) + get_positive_points_status = SparseIR.spir_sampling_get_npoints( + sampling_positive_only, n_points_positive_only_ref) + @test get_positive_points_status == SparseIR.SPIR_COMPUTATION_SUCCESS + n_points_positive_only = n_points_positive_only_ref[] + @test n_points_positive_only > 0 + + # Clean up + SparseIR.spir_sampling_release(sampling_positive_only) + SparseIR.spir_sampling_release(sampling) + SparseIR.spir_basis_release(basis) + SparseIR.spir_sve_result_release(sve) + SparseIR.spir_kernel_release(kernel) + end + + @testset "MatsubaraSampling Constructor (fermionic)" begin + test_matsubara_sampling_constructor(SparseIR.SPIR_STATISTICS_FERMIONIC) + end + + @testset "MatsubaraSampling Constructor (bosonic)" begin + test_matsubara_sampling_constructor(SparseIR.SPIR_STATISTICS_BOSONIC) + end +end diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 580d9a6..0000000 --- a/test/Project.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "SparseIR Tests" - -[deps] -Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" diff --git a/test/_conftest.jl b/test/_conftest.jl deleted file mode 100644 index b555951..0000000 --- a/test/_conftest.jl +++ /dev/null @@ -1,10 +0,0 @@ -@testmodule CommonTestData begin - using SparseIR: SVEResult, LogisticKernel - - const sve_logistic = Dict( - 10 => SVEResult(LogisticKernel(10.0)), - 42 => SVEResult(LogisticKernel(42.0)), - 10_000 => SVEResult(LogisticKernel(10_000.0)), - (10_000, 1e-12) => SVEResult(LogisticKernel(10_000.0); ε=1e-12) - ) -end diff --git a/test/_linalg.jl b/test/_linalg.jl deleted file mode 100644 index 67a504a..0000000 --- a/test/_linalg.jl +++ /dev/null @@ -1,168 +0,0 @@ -@testitem "jacobi with T = $T" begin - using LinearAlgebra - - for T in (Float64, SparseIR.Float64x2) - A = T.(randn(20, 10)) - U, S, V = SparseIR._LinAlg.svd_jacobi(A) - @test U * Diagonal(S) * V' ≈ A - end -end - -@testitem "rrqr with T = $T" begin - using LinearAlgebra - - for T in (Float64, SparseIR.Float64x2) - A = T.(randn(40, 30)) - A_eps = norm(A) * eps(eltype(A)) - A_qr, A_rank = SparseIR._LinAlg.rrqr(A) - A_rec = A_qr.Q * A_qr.R * A_qr.P' - @test isapprox(A_rec, A; rtol=0, atol=4 * A_eps) - @test A_rank == 30 - end -end - -@testitem "rrqr_trunc with T = $T" begin - using LinearAlgebra - - for T in (Float64, SparseIR.Float64x2) - # Vandermonde matrix - A = Vector{T}(-1:0.02:1) .^ Vector(0:20)' - m, n = size(A) - A_qr, k = SparseIR._LinAlg.rrqr(A; rtol=1e-5) - @test k < min(m, n) - - Q, R = SparseIR._LinAlg.truncate_qr_result(A_qr, k) - A_rec = Q * R * A_qr.P' - @test isapprox(A, A_rec, rtol=0, atol=1e-5 * norm(A)) - end -end - -@testitem "tsvd with T = $T" begin - using LinearAlgebra - - for T in (Float64, SparseIR.Float64x2), tol in (1e-14, 1e-13) - A = Vector{T}(-1:0.01:1) .^ Vector(0:50)' - U, S, V = SparseIR._LinAlg.tsvd(A; rtol=tol) - k = length(S) - - @test U * Diagonal(S) * V'≈A rtol=0 atol=tol * norm(A) - @test U'U ≈ I - @test V'V ≈ I - @test issorted(S; rev=true) - @test k < minimum(size(A)) - - A_svd = svd(Float64.(A)) - @test S ≈ A_svd.S[1:k] - end -end - -@testitem "svd of VERY triangular 2x2 with T = $T" begin - for T in (Float64, SparseIR.Float64x2) - (cu, su), (smax, smin), (cv, sv) = SparseIR._LinAlg.svd2x2(T(1), T(1e100), T(1)) - @test cu ≈ 1.0 - @test su ≈ 1e-100 - @test smax ≈ 1e100 - @test smin ≈ 1e-100 - @test cv ≈ 1e-100 - @test sv ≈ 1.0 - U = [cu -su - su cu] - S = [smax 0 - 0 smin] - Vt = [cv sv - -sv cv] - A = [T(1) T(1e100) - T(0) T(1)] - @test U * S * Vt ≈ A - - (cu, su), (smax, smin), (cv, sv) = SparseIR._LinAlg.svd2x2(T(1), T(1e100), T(1e100)) - @test cu ≈ 1 / √2 - @test su ≈ 1 / √2 - @test smax ≈ √2 * 1e100 - @test smin ≈ 1 / √2 - @test cv ≈ 5e-101 - @test sv ≈ 1.0 - U = [cu -su - su cu] - S = [smax 0 - 0 smin] - Vt = [cv sv - -sv cv] - A = [T(1) T(1e100) - T(0) T(1e100)] - @test U * S * Vt ≈ A - - (cu, su), (smax, smin), (cv, sv) = SparseIR._LinAlg.svd2x2(T(1e100), T(1e200), T(2)) - @test cu ≈ 1.0 - @test su ≈ 2e-200 - @test smax ≈ 1e200 - @test smin ≈ 2e-100 - @test cv ≈ 1e-100 - @test sv ≈ 1.0 - U = [cu -su - su cu] - S = [smax 0 - 0 smin] - Vt = [cv sv - -sv cv] - A = [T(1e100) T(1e200) - T(0) T(2)] - @test U * S * Vt ≈ A - - (cu, su), (smax, smin), - (cv, sv) = SparseIR._LinAlg.svd2x2(T(1e-100), T(1), - T(1e-100)) - @test cu ≈ 1.0 - @test su ≈ 1e-100 - @test smax ≈ 1.0 - @test smin ≈ 1e-200 - @test cv ≈ 1e-100 - @test sv ≈ 1.0 - U = [cu -su - su cu] - S = [smax 0 - 0 smin] - Vt = [cv sv - -sv cv] - A = [T(1e-100) T(1) - T(0) T(1e-100)] - @test U * S * Vt ≈ A - end -end - -@testitem "svd of 'more lower' 2x2 with T = $T" begin - for T in (Float64, SparseIR.Float64x2) - (cu, su), (smax, smin), - (cv, sv) = SparseIR._LinAlg.svd2x2(T(1), T(1e-100), - T(1e100), T(1)) - @test cu ≈ 1e-100 - @test su ≈ 1.0 - @test smax ≈ 1e100 - @test abs(smin) < 1e-100 # should be ≈ 0.0, but x ≈ 0 is equivalent to x == 0 - @test cv ≈ 1.0 - @test sv ≈ 1e-100 - U = [cu -su - su cu] - S = [smax 0 - 0 smin] - Vt = [cv sv - -sv cv] - A = [T(1) T(1e-100) - T(1e100) T(1)] - @test U * S * Vt ≈ A - end -end - -@testitem "givens rotation of 2d vector - special cases with T = $T" begin - for T in (Float64, SparseIR.Float64x2) - for v in ([42, 0], [-42, 0], [0, 42], [0, -42], [0, 0]) - v = T.(v) - (c, s), r = SparseIR._LinAlg.givens_params(v...) - R = [c s - -s c] - Rv = [r - T(0)] - @test R * v == Rv - end - end -end diff --git a/test/_multifloat_funcs.jl b/test/_multifloat_funcs.jl deleted file mode 100644 index 8daaf2a..0000000 --- a/test/_multifloat_funcs.jl +++ /dev/null @@ -1,34 +0,0 @@ -@testsnippet MultiFloatSetup begin - logrange(x1, x2, length) = (exp10(y) for y in range(log10(x1), log10(x2); length)) - - xx = collect(logrange(floatmin(Float64), 20.0, 1000)) - xx = [0.0] ∪ xx ∪ [Inf] - xx = -xx ∪ xx - xx = SparseIR.Float64x2.(xx) -end - -@testitem "type inference and stability" setup=[MultiFloatSetup] begin - x = rand(xx) - @inferred sinh(x) - @inferred cosh(x) - - @test sinh(x) isa SparseIR.Float64x2 - @test cosh(x) isa SparseIR.Float64x2 -end - -@testitem "sinh(x)" setup=[MultiFloatSetup] begin - for x in xx - @test sinh(x)≈sinh(big(x)) rtol=eps(SparseIR.Float64x2) - end -end - -@testitem "cosh(x)" setup=[MultiFloatSetup] begin - for x in xx - @test cosh(x)≈cosh(big(x)) rtol=eps(SparseIR.Float64x2) - end -end - -@testitem "Float64x2(NaN) sinh/cosh" setup=[MultiFloatSetup] begin - @test isnan(sinh(SparseIR.Float64x2(NaN))) - @test isnan(cosh(SparseIR.Float64x2(NaN))) -end diff --git a/test/_roots.jl b/test/_roots.jl deleted file mode 100644 index 5b36b43..0000000 --- a/test/_roots.jl +++ /dev/null @@ -1,111 +0,0 @@ -@testitem "refine_grid - Basic refinement" begin - # Test with α = 2 - grid = [0.0, 1.0, 2.0] - refined = @inferred SparseIR.refine_grid(grid, Val(2)) - @test length(refined) == 5 # α * (n-1) + 1 = 2 * (3-1) + 1 = 5 - @test refined ≈ [0.0, 0.5, 1.0, 1.5, 2.0] - - # Test with α = 3 - refined3 = SparseIR.refine_grid(grid, Val(3)) - @test length(refined3) == 7 # α * (n-1) + 1 = 3 * (3-1) + 1 = 7 - @test refined3 ≈ [0.0, 1 / 3, 2 / 3, 1.0, 4 / 3, 5 / 3, 2.0] - - # Test with α = 4 - refined4 = SparseIR.refine_grid(grid, Val(4)) - @test length(refined4) == 9 # α * (n-1) + 1 = 4 * (3-1) + 1 = 9 - @test refined4 ≈ [0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] -end - -#= -Currently the SparseIR.refine_grid function is used only for the -SparseIR.find_all function, which should accept only Float64 grids, but -just in case it is a good idea to test that it works for other types. -=# -@testitem "refine_grid - Type stability" begin - # Integer grid - int_grid = [0, 1, 2] - refined = @inferred SparseIR.refine_grid(int_grid, Val(2)) - @test eltype(refined) === Float64 - @test refined == [0.0, 0.5, 1.0, 1.5, 2.0] - - # Float32 grid - f32_grid = Float32[0, 1, 2] - refined_f32 = @inferred SparseIR.refine_grid(f32_grid, Val(2)) - @test eltype(refined_f32) === Float32 - @test refined_f32 ≈ Float32[0, 0.5, 1, 1.5, 2] -end - -@testitem "refine_grid - Edge cases" begin - # Single interval - single_interval = [0.0, 1.0] - refined_single = SparseIR.refine_grid(single_interval, Val(4)) - @test length(refined_single) == 5 # α * (2-1) + 1 = 4 * 1 + 1 = 5 - @test refined_single ≈ [0.0, 0.25, 0.5, 0.75, 1.0] - - # Empty grid - empty_grid = Float64[] - @test isempty(SparseIR.refine_grid(empty_grid, Val(2))) - # Empty grid - empty_grid = Int[] - out_grid = SparseIR.refine_grid(empty_grid, Val(2)) - @test isempty(out_grid) - @test eltype(out_grid) === Float64 - # Single point - single_point = [1.0] - @test SparseIR.refine_grid(single_point, Val(2)) == [1.0] -end - -@testitem "refine_grid - Uneven spacing" begin - # Test with unevenly spaced grid - uneven = [0.0, 1.0, 10.0] - refined_uneven = SparseIR.refine_grid(uneven, Val(2)) - @test length(refined_uneven) == 5 - @test refined_uneven[1:3] ≈ [0.0, 0.5, 1.0] # First interval - @test refined_uneven[3:5] ≈ [1.0, 5.5, 10.0] # Second interval -end - -@testitem "refine_grid - Preservation of endpoints" begin - grid = [-1.0, 0.0, 1.0] - for α in [2, 3, 4] - refined = SparseIR.refine_grid(grid, Val(α)) - @test first(refined) == first(grid) - @test last(refined) == last(grid) - end -end - -@testitem "discrete_extrema" begin - nonnegative = collect(0:8) - symmetric = collect(-8:8) - @test SparseIR.discrete_extrema(x -> x, nonnegative) == [8] - @test SparseIR.discrete_extrema(x -> x - eps(), nonnegative) == [0, 8] - @test SparseIR.discrete_extrema(x -> x^2, symmetric) == [-8, 0, 8] - @test SparseIR.discrete_extrema(x -> 1, symmetric) == [] -end - -@testitem "midpoint" begin - @test SparseIR.midpoint(typemax(Int), typemax(Int)) === typemax(Int) - @test SparseIR.midpoint(typemin(Int), typemax(Int)) === -1 - @test SparseIR.midpoint(typemin(Int), typemin(Int)) === typemin(Int) - @test SparseIR.midpoint(Int16(1000), Int32(2000)) === Int32(1500) - @test SparseIR.midpoint(floatmax(Float64), floatmax(Float64)) === floatmax(Float64) - @test SparseIR.midpoint(Float16(0), floatmax(Float32)) === floatmax(Float32) / 2 - @test SparseIR.midpoint(Float16(0), floatmax(BigFloat)) == floatmax(BigFloat) / 2 - @test SparseIR.midpoint(Int16(0), big"99999999999999999999") == - big"99999999999999999999" ÷ 2 - @test SparseIR.midpoint(-10.0, 1) === -4.5 -end - -@testitem "sorted output" begin - λ = 1e1 - β = 1e2 - eps = 1e-10 - wmax = λ / β - basis = FiniteTempBasis{Fermionic}(β, wmax, eps) - - part = nothing - grid = SparseIR.DEFAULT_GRID - û = last(basis.uhat_full) - f = SparseIR.func_for_part(û, part) - x₀ = SparseIR.discrete_extrema(f, grid) - @test issorted(x₀) -end diff --git a/test/_specfuncs.jl b/test/_specfuncs.jl deleted file mode 100644 index 4b4695a..0000000 --- a/test/_specfuncs.jl +++ /dev/null @@ -1,21 +0,0 @@ -@testitem "_specfuncs - legder" begin - @test SparseIR.legder(rand(10, 20), 99) == zeros((1, 20)) - @test SparseIR.legder([1 2 3 4]') == [6 9 20]' - @test SparseIR.legder([1 2 3 4]', 2) == [9 60]' - @test SparseIR.legder([1 2 3 4]', 3) == [60]' -end - -@testitem "_specfuncs - legder edge case: cnt >= n" begin - # Test line 56: when derivative order >= number of rows, return zeros(T, (1, m)) - c = [1.0 2.0; 3.0 4.0; 5.0 6.0] # 3×2 matrix - - # cnt = n (equal case) - result = SparseIR.legder(c, 3) - @test result == zeros(1, 2) - @test size(result) == (1, 2) - - # cnt > n (greater case) - result = SparseIR.legder(c, 5) - @test result == zeros(1, 2) - @test size(result) == (1, 2) -end diff --git a/test/aqua.jl b/test/aqua.jl deleted file mode 100644 index 68c1fa3..0000000 --- a/test/aqua.jl +++ /dev/null @@ -1,5 +0,0 @@ -@testitem "Aqua.jl" begin - using Aqua - - Aqua.test_all(SparseIR; piracies=false) -end diff --git a/test/aqua_tests.jl b/test/aqua_tests.jl new file mode 100644 index 0000000..010e81b --- /dev/null +++ b/test/aqua_tests.jl @@ -0,0 +1,7 @@ +@testitem "Aqua" begin + using Test + import Aqua + @testset "Aqua" begin + Aqua.test_all(Tensor4All; deps_compat=false) + end +end diff --git a/test/augment.jl b/test/augment.jl deleted file mode 100644 index 8dbd977..0000000 --- a/test/augment.jl +++ /dev/null @@ -1,142 +0,0 @@ -@testitem "Augmented bosonic basis" begin - using LinearAlgebra - - ωmax = 2 - β = 1000 - basis = FiniteTempBasis{Bosonic}(β, ωmax, 1e-6) - basis_aug = AugmentedBasis(basis, TauConst, TauLinear) - - @test all(isone, SparseIR.significance(basis_aug)[1:3]) - - # G(τ) = c - e^{-τ * pole} / (1 - e^{-β * pole}) - pole = 1.0 - c = 1e-2 - τ_smpl = TauSampling(basis_aug) - @test length(τ_smpl.τ) == length(basis_aug) - gτ = c .+ transpose(basis.u(τ_smpl.τ)) * (-basis.s .* basis.v(pole)) - magn = maximum(abs, gτ) - - # This illustrates that "naive" fitting is a problem if the fitting matrix - # is not well-conditioned. - gl_fit_bad = pinv(τ_smpl.matrix) * gτ - gτ_reconst_bad = evaluate(τ_smpl, gl_fit_bad) - @test !isapprox(gτ_reconst_bad, gτ; atol=1e-13 * magn) - @test isapprox(gτ_reconst_bad, gτ, atol=5e-16 * cond(τ_smpl) * magn) - @test cond(τ_smpl) > 1e7 - @test size(τ_smpl.matrix) == (length(basis_aug), length(τ_smpl.τ)) - - # Now do the fit properly - gl_fit = fit(τ_smpl, gτ) - gτ_reconst = evaluate(τ_smpl, gl_fit) - - @test isapprox(gτ_reconst, gτ, atol=1e-14 * magn) -end - -@testitem "Vertex basis" begin - for stat in (Fermionic(), Bosonic()) - ωmax = 2 - β = 1000 - basis = FiniteTempBasis(stat, β, ωmax, 1e-6) - basis_aug = AugmentedBasis(basis, MatsubaraConst) - @test !isnothing(basis_aug.uhat) - - # G(iν) = c + 1 / (iν - pole) - pole = 1.0 - c = 1.0 - matsu_smpl = MatsubaraSampling(basis_aug) - giν = @. c + 1 / (SparseIR.valueim(matsu_smpl.ωn, β) - pole) - gl = fit(matsu_smpl, giν) - - giν_reconst = evaluate(matsu_smpl, gl) - - @test isapprox(giν_reconst, giν, atol=maximum(abs, giν) * 1e-7) - end -end - -@testsnippet AugmentedBasisSetup begin - β = 1000 - ωmax = 2 - basis = FiniteTempBasis{Bosonic}(β, ωmax, 1e-6) - basis_aug = AugmentedBasis(basis, TauConst, TauLinear) -end - -@testitem "getindex" setup=[AugmentedBasisSetup] begin - @test length(basis_aug.u[1:5]) == 5 - @test_throws ErrorException basis_aug.u[1:2] - @test_throws ErrorException basis_aug.u[3:7] - @test basis_aug.u[1] isa TauConst - @test basis_aug.u[2] isa TauLinear - - @test length(basis_aug[1:5]) == 5 - @test_throws ErrorException basis_aug[1:2] - @test_throws ErrorException basis_aug[3:7] -end - -@testitem "accessor functions" setup=[AugmentedBasisSetup] begin - len_basis = length(basis) - len_aug = len_basis + 2 - - @test size(basis_aug) == (len_aug,) - @test SparseIR.accuracy(basis_aug) == SparseIR.accuracy(basis) - @test SparseIR.Λ(basis_aug) == β * ωmax - @test SparseIR.ωmax(basis_aug) == ωmax - - @test size(basis_aug.u) == (len_aug,) - @test length(basis_aug.u(0.8)) == len_aug - @test length(basis_aug.uhat(MatsubaraFreq(4))) == len_aug - @test SparseIR.xmin(basis_aug.u) == 0.0 - @test SparseIR.xmax(basis_aug.u) == β - - @test SparseIR.deriv(basis_aug.u)(0.8)[3:end] == - SparseIR.PiecewiseLegendrePolyVector(SparseIR.deriv.(SparseIR.fbasis(basis_aug.u)))(0.8) - - @test SparseIR.zeta(basis_aug.uhat) == 0 -end - -@testitem "create" setup=[AugmentedBasisSetup] begin - @test SparseIR.create(MatsubaraConst(42), basis) == MatsubaraConst(42) - @test SparseIR.create(MatsubaraConst, basis) == MatsubaraConst(β) -end - -@testitem "TauConst" begin - @test_throws DomainError TauConst(-34) - tc = TauConst(123) - @test SparseIR.β(tc) == 123.0 - @test_throws DomainError tc(-3) - @test_throws DomainError tc(321) - @test tc(100) == 1 / sqrt(123) - @test tc(MatsubaraFreq(0)) == sqrt(123) - @test tc(MatsubaraFreq(92)) == 0.0 - @test_throws ErrorException tc(MatsubaraFreq(93)) - @test SparseIR.deriv(tc)(4.2) == 0.0 - @test SparseIR.deriv(tc, Val(0)) == tc -end - -@testitem "TauLinear" begin - @test_throws DomainError TauLinear(-34) - tl = TauLinear(123) - @test SparseIR.β(tl) == 123.0 - @test_throws DomainError tl(-3) - @test_throws DomainError tl(321) - @test tl(100) ≈ sqrt(3 / 123) * (2 / 123 * 100 - 1) - @test tl(MatsubaraFreq(0)) == 0.0 - @test tl(MatsubaraFreq(92)) ≈ sqrt(3 / 123) * 2 / im * 123 / (92 * π) - @test_throws ErrorException tl(MatsubaraFreq(93)) - @test SparseIR.deriv(tl, Val(0)) == tl - @test SparseIR.deriv(tl)(4.2) ≈ sqrt(3 / 123) * 2 / 123 - @test SparseIR.deriv(tl, Val(2))(4.2) == 0.0 -end - -@testitem "MatsubaraConst" begin - @test_throws DomainError MatsubaraConst(-34) - mc = MatsubaraConst(123) - @test SparseIR.β(mc) == 123.0 - @test_throws DomainError mc(-3) - @test_throws DomainError mc(321) - @test isnan(mc(100)) - @test mc(MatsubaraFreq(0)) == 1.0 - @test mc(MatsubaraFreq(92)) == 1.0 - @test mc(MatsubaraFreq(93)) == 1.0 - @test SparseIR.deriv(mc) == mc - @test SparseIR.deriv(mc, Val(0)) == mc -end diff --git a/test/basis.jl b/test/basis.jl deleted file mode 100644 index eaa73ea..0000000 --- a/test/basis.jl +++ /dev/null @@ -1,83 +0,0 @@ -@testitem "consistency" begin - β = 2.0 - ωmax = 5.0 - ε = 1e-5 - basis_f, basis_b = SparseIR.finite_temp_bases(β, ωmax, ε) - bs = FiniteTempBasisSet(β, ωmax, ε) - - @test length(bs.basis_f) == length(basis_f) - @test length(bs.basis_b) == length(basis_b) -end - -@testitem "consistency2" setup=[CommonTestData] begin - β = 2.0 - ωmax = 5.0 - ε = 1e-5 - - sve_result = CommonTestData.sve_logistic[β * ωmax] - basis_f, basis_b = SparseIR.finite_temp_bases(β, ωmax, ε; sve_result) - smpl_τ_f = TauSampling(basis_f) - smpl_τ_b = TauSampling(basis_b) - smpl_wn_f = MatsubaraSampling(basis_f) - smpl_wn_b = MatsubaraSampling(basis_b) - - bs = FiniteTempBasisSet(β, ωmax, ε; sve_result) - @test smpl_τ_f.sampling_points == smpl_τ_b.sampling_points - @test bs.smpl_tau_f.matrix == smpl_τ_f.matrix - @test bs.smpl_tau_b.matrix == smpl_τ_b.matrix - - @test bs.smpl_wn_f.matrix == smpl_wn_f.matrix - @test bs.smpl_wn_b.matrix == smpl_wn_b.matrix -end - -@testitem "FiniteTempBasis" begin - using Logging: with_logger, NullLogger - - with_logger(NullLogger()) do - basis = FiniteTempBasis{Fermionic}(1e-3, 1e-3, 1e-100) - @test SparseIR.sve_result(basis).s * sqrt(1e-3 / 2 * 1e-3) ≈ basis.s - @test SparseIR.accuracy(basis) ≈ last(basis.s) / first(basis.s) - end - basis = FiniteTempBasis{Fermionic}(3, 4, 1e-6) - - @test SparseIR.ωmax(SparseIR.rescale(basis, 2)) ≈ 6 - - sve = SparseIR.sve_result(basis) - - @test_logs (:warn, r""" - Expecting to get 100 sampling points for corresponding basis function, - instead got \d+\. This may happen if not enough precision is - left in the polynomial\. - """) SparseIR.default_sampling_points(sve.u, 100) - - basis = FiniteTempBasis{Bosonic}(3, 4, 1e-6) - - @test_logs (:warn, r""" - Requesting 13 SparseIR.Bosonic\(\) sampling frequencies for basis size - L = \d+, but \d+ were returned\. This may indicate a problem with precision\. - """) SparseIR.default_matsubara_sampling_points( - basis.uhat, 12; fence=true) -end - -@testitem "unit tests LogisticKernel" begin - β = 23 - ωmax = 3e-2 - ε = 1e-5 - - bset = FiniteTempBasisSet(β, ωmax, ε) - - basis_f = FiniteTempBasis{Fermionic}(β, ωmax, ε) - basis_b = FiniteTempBasis{Bosonic}(β, ωmax, ε) - @test SparseIR.β(bset) == β - @test SparseIR.ωmax(bset) == ωmax - @test bset.tau == SparseIR.sampling_points(TauSampling(basis_f)) - @test bset.wn_f == SparseIR.sampling_points(MatsubaraSampling(basis_f)) - @test bset.wn_b == SparseIR.sampling_points(MatsubaraSampling(basis_b)) - @test bset.sve_result.s ≈ SparseIR.SVEResult(LogisticKernel(β * ωmax); ε).s - @test :tau ∈ propertynames(bset) - SparseIR.finite_temp_bases(0.1, 0.2, 1e-3; kernel=RegularizedBoseKernel(0.1 * 0.2)) - - Λ = 10.0 - kernel = RegularizedBoseKernel(Λ) - SparseIR.SVEResult(kernel; ε=1e-10) -end diff --git a/test/dlr.jl b/test/dlr.jl deleted file mode 100644 index be87bf4..0000000 --- a/test/dlr.jl +++ /dev/null @@ -1,95 +0,0 @@ -@testitem "Compression" setup=[CommonTestData] begin - using Random - - for stat in (Fermionic(), Bosonic()) - β = 10_000 - ωmax = 1 - ε = 1e-12 - basis = FiniteTempBasis( - stat, β, ωmax, ε; sve_result=CommonTestData.sve_logistic[β * ωmax]) - dlr = DiscreteLehmannRepresentation(basis) - - Random.seed!(982743) - - num_poles = 10 - poles = ωmax * (2rand(num_poles) .- 1) - coeffs = 2rand(num_poles) .- 1 - @test maximum(abs, poles) ≤ ωmax - - Gl = SparseIR.to_IR(DiscreteLehmannRepresentation(basis, poles), coeffs) - g_dlr = SparseIR.from_IR(dlr, Gl) - - # Comparison on Matsubara frequencies - smpl = MatsubaraSampling(basis) - smpl_for_dlr = MatsubaraSampling(dlr; - sampling_points=SparseIR.sampling_points(smpl)) - - giv_ref = evaluate(smpl, Gl; dim=1) - giv = evaluate(smpl_for_dlr, g_dlr) - - @test isapprox(giv, giv_ref; atol=300ε, rtol=0) - - # Comparison on τ - smpl_τ = TauSampling(basis) - gτ = evaluate(smpl_τ, Gl) - - smpl_τ_for_dlr = TauSampling(dlr) - gτ2 = evaluate(smpl_τ_for_dlr, g_dlr) - - @test isapprox(gτ, gτ2; atol=300ε, rtol=0) - end -end - -@testitem "Boson" setup=[CommonTestData] begin - β = 2 - ωmax = 21 - ε = 1e-7 - basis_b = FiniteTempBasis{Bosonic}( - β, ωmax, ε; sve_result=CommonTestData.sve_logistic[β * ωmax]) - - # G(iw) = sum_p coeff_p U^{DLR}(iw, omega_p) - coeff = [1.1, 2.0] - ω_p = [2.2, -1.0] - - ρl_pole = basis_b.v(ω_p) * coeff - gl_pole = -basis_b.s .* ρl_pole - - sp = DiscreteLehmannRepresentation(basis_b, ω_p) - gl_pole2 = SparseIR.to_IR(sp, coeff) - - @test isapprox(gl_pole, gl_pole2; atol=300ε, rtol=0) -end - -@testitem "MatsubaraPoles" begin - poles = [2.0, 3.3, 9.3] - β = π - - n = rand(-12345:2:987, 100) - mpb = SparseIR.MatsubaraPoles{Fermionic}(β, poles) - @test mpb(n) ≈ @. 1 / (im * n' - poles) - - n = rand(-234:2:13898, 100) - mbp = SparseIR.MatsubaraPoles{Bosonic}(β, poles) - @test mbp(n) ≈ @. tanh(π / 2 * poles) / (im * n' - poles) -end - -@testitem "DiscreteLehmannRepresentation" setup=[CommonTestData] begin - for stat in (Fermionic(), Bosonic()) - β = 10_000 - ωmax = 1 - ε = 1e-12 - basis = FiniteTempBasis( - stat, β, ωmax, ε; sve_result=CommonTestData.sve_logistic[β * ωmax]) - dlr = DiscreteLehmannRepresentation(basis) - - @test all(isone, SparseIR.significance(dlr)) - @test SparseIR.β(dlr) == β - @test SparseIR.ωmax(dlr) == ωmax - @test SparseIR.Λ(dlr) == β * ωmax - @test SparseIR.sampling_points(dlr) == - SparseIR.default_omega_sampling_points(basis) - @test SparseIR.accuracy(dlr) < ε - @test SparseIR.default_matsubara_sampling_points(dlr) == - SparseIR.default_matsubara_sampling_points(basis) - end -end diff --git a/test/freq.jl b/test/freq.jl deleted file mode 100644 index 3bff68e..0000000 --- a/test/freq.jl +++ /dev/null @@ -1,83 +0,0 @@ -@testitem "freq" begin - @test SparseIR.zeta(MatsubaraFreq(2)) == 0 - @test SparseIR.zeta(MatsubaraFreq(-5)) == 1 - - @test Int(FermionicFreq(3)) === 3 - @test Int(BosonicFreq(-2)) === -2 - - @test Int(MatsubaraFreq(Int32(4))) === 4 - - @test_throws DomainError FermionicFreq(4) - @test_throws DomainError BosonicFreq(-7) - - @test FermionicFreq(5) < BosonicFreq(6) - @test BosonicFreq(6) >= BosonicFreq(6) - - @test SparseIR.value(pioverbeta, 3) == π / 3 - @test SparseIR.valueim(2 * pioverbeta, 3) == 2im * π / 3 - - @test !iszero(MatsubaraFreq(-3)) -end - -@testitem "freqadd" begin - @test +pioverbeta == pioverbeta - @test iszero(pioverbeta - pioverbeta) - - @test pioverbeta + oneunit(pioverbeta) == 2 * pioverbeta - @test Int(4 * pioverbeta) == 4 - @test Int(pioverbeta - 2 * pioverbeta) == -1 - @test iszero(zero(2 * pioverbeta)) -end - -@testitem "freqrange" begin - @test length(FermionicFreq(1):FermionicFreq(-3)) == 0 - @test length(FermionicFreq(3):FermionicFreq(200_000_000_001)) == - 100_000_000_000 - - @test collect(BosonicFreq(2):BosonicFreq(-2)) == [] - @test collect(BosonicFreq(0):BosonicFreq(4)) == (0:2:4) .* pioverbeta - @test collect(FermionicFreq(-3):FermionicFreq(1)) == (-3:2:1) .* pioverbeta - - @test length(FermionicFreq(37):BosonicFreq(10):FermionicFreq(87)) == 6 - @test collect(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(58)) == - (-10:4:58) .* pioverbeta - @test collect(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(60)) == - (-10:4:58) .* pioverbeta - @test length(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(60)) == 18 - @test length(FermionicFreq(1):BosonicFreq(100):FermionicFreq(3)) == 1 - @test length(FermionicFreq(1):BosonicFreq(100):FermionicFreq(-1001)) == 0 - - @test_throws MethodError FermionicFreq(5):BosonicFreq(100) - @test_throws MethodError BosonicFreq(6):FermionicFreq(3):BosonicFreq(100) - @test_throws MethodError FermionicFreq(7):FermionicFreq(3):FermionicFreq(101) -end - -@testitem "freqinvalid" begin - @test_throws ArgumentError BosonicFreq(2)==2 - @test_throws ArgumentError FermionicFreq(1)-1 -end - -@testitem "unit tests" begin - @test_throws DomainError SparseIR.Statistics(2) - @test SparseIR.Statistics(0) + SparseIR.Statistics(1) == Fermionic() - @test Integer(FermionicFreq(19)) == 19 - @test -BosonicFreq(-24) == BosonicFreq(24) - @test sign(BosonicFreq(24)) == 1 - @test sign(BosonicFreq(0)) == 0 - @test sign(BosonicFreq(-94)) == -1 - @test BosonicFreq(24) % FermionicFreq(-7) == FermionicFreq(3) - @test FermionicFreq(123) % FermionicFreq(9) == BosonicFreq(6) - @test promote_type(BosonicFreq, FermionicFreq) == MatsubaraFreq -end - -@testitem "findfirst (needs dimensionless div)" begin - @test BosonicFreq(100) ÷ BosonicFreq(50) === 2 - @test FermionicFreq(101) ÷ BosonicFreq(50) === 2 - haystack = FermionicFreq(-31):BosonicFreq(6):FermionicFreq(111) - needle = FermionicFreq(23) - @test findfirst(==(needle), haystack) === 10 # 6 * (10-1) + (-31) == 23 - @test haystack[findfirst(==(needle), haystack)] === needle - @test isnothing(findfirst(==(needle - oneunit(needle)), haystack)) - @test findfirst(==(first(haystack)), haystack) === 1 - @test findfirst(==(last(haystack)), haystack) === length(haystack) -end diff --git a/test/gauss.jl b/test/gauss.jl deleted file mode 100644 index c02bb26..0000000 --- a/test/gauss.jl +++ /dev/null @@ -1,39 +0,0 @@ -@testsnippet ValidateGaussFunction begin - function validategauss(rule) - @test rule.a ≤ rule.b - @test all(≤(rule.b), rule.x) - @test all(≥(rule.a), rule.x) - @test issorted(rule.x) - @test length(rule.x) == length(rule.w) - @test rule.x_forward ≈ rule.x .- rule.a - @test rule.x_backward ≈ rule.b .- rule.x - end -end - -@testitem "collocate" begin - using LinearAlgebra - - r = SparseIR.legendre(20) - cmat = SparseIR.legendre_collocation(r) - emat = SparseIR.legvander(r.x, length(r.x) - 1) - @test emat * cmat ≈ I(20) -end - -@testitem "gauss legendre" setup=[ValidateGaussFunction] begin - rule = SparseIR.legendre(200) - validategauss(rule) - x, w = SparseIR.gauss(200) - @test rule.x ≈ x - @test rule.w ≈ w -end - -@testitem "piecewise" setup=[ValidateGaussFunction] begin - edges = [-4, -1, 1, 3] - rule = SparseIR.piecewise(SparseIR.legendre(20), edges) - validategauss(rule) -end - -@testitem "scale" setup=[ValidateGaussFunction] begin - rule = SparseIR.legendre(30) - validategauss(SparseIR.scale(rule, 2)) -end diff --git a/test/jet.jl b/test/jet.jl deleted file mode 100644 index 46a151b..0000000 --- a/test/jet.jl +++ /dev/null @@ -1,47 +0,0 @@ -@testitem "matsubara" tags=[:jet_tests] begin - using JET - - beta = 2.0 - wmax = 3.0 - rtol = 1e-8 - basis = FiniteTempBasis{Fermionic}(beta, wmax, rtol) - - sampling = MatsubaraSampling(basis) - wsample = sampling_points(sampling) - G_matsubara = rand(ComplexF64, length(wsample)) - @test_opt fit(sampling, G_matsubara) - - G_l = fit(sampling, G_matsubara) - @test_opt fit!(G_l, sampling, G_matsubara) - - workarr = Vector{eltype(G_l)}( - undef, SparseIR.workarrlength(sampling, G_matsubara)) - @test_opt fit!(G_l, sampling, G_matsubara; workarr) - - @test_opt evaluate(sampling, G_l) - - @test_opt evaluate!(G_matsubara, sampling, G_l) -end - -@testitem "imaginary time" tags=[:jet_tests] begin - using JET - - beta = 2.0 - wmax = 3.0 - rtol = 1e-8 - basis = FiniteTempBasis{Fermionic}(beta, wmax, rtol) - - sampling = TauSampling(basis) - wsample = sampling_points(sampling) - G_tau = rand(ComplexF64, length(wsample)) - @test_opt fit(sampling, G_tau) - - G_l = fit(sampling, G_tau) - @test_opt fit!(G_l, sampling, G_tau) - - workarr = Vector{eltype(G_l)}(undef, SparseIR.workarrlength(sampling, G_tau)) - @test_opt fit!(G_l, sampling, G_tau; workarr) - - @test_opt evaluate(sampling, G_l) - @test_opt evaluate!(G_tau, sampling, G_l) -end diff --git a/test/kernel.jl b/test/kernel.jl deleted file mode 100644 index 63e7b00..0000000 --- a/test/kernel.jl +++ /dev/null @@ -1,60 +0,0 @@ -@testitem "accuracy" begin - for K in (LogisticKernel(9.0), - RegularizedBoseKernel(8.0), - LogisticKernel(120_000.0), - RegularizedBoseKernel(127_500.0), - SparseIR.get_symmetrized(LogisticKernel(40_000.0), - -1), - SparseIR.get_symmetrized(RegularizedBoseKernel(35_000.0), - -1)) - T = Float32 - T_x = Float64 - - rule = convert(SparseIR.Rule{T}, SparseIR.legendre(10)) - hints = SparseIR.sve_hints(K, eps(T_x)) - gauss_x = SparseIR.piecewise(rule, SparseIR.segments_x(hints)) - gauss_y = SparseIR.piecewise(rule, SparseIR.segments_y(hints)) - ϵ = eps(T) - tiny = floatmin(T) / ϵ - - result = SparseIR.matrix_from_gauss(K, gauss_x, gauss_y) - result_x = SparseIR.matrix_from_gauss(K, convert(SparseIR.Rule{T_x}, gauss_x), - convert(SparseIR.Rule{T_x}, gauss_y)) - magn = maximum(abs, result_x) - - @test result≈result_x atol=2magn * ϵ rtol=0 - reldiff = @. ifelse(abs(result) < tiny, 1.0, result / result_x) - @test reldiff≈ones(size(reldiff)) atol=100ϵ rtol=0 - end -end - -@testitem "singularity" begin - for Λ in (10, 42, 10_000), x in 2rand(10) .- 1 - K = RegularizedBoseKernel(Λ) - @test K(x, 0.0) ≈ 1 / Λ - end -end - -@testitem "unit tests" begin - K = LogisticKernel(42) - K_symm = SparseIR.get_symmetrized(K, 1) - @test !SparseIR.iscentrosymmetric(K_symm) - @test_throws ErrorException SparseIR.get_symmetrized(K_symm, -1) - @test SparseIR.weight_func(K, Bosonic())(1e-8) == 1 / tanh(0.5 * 42 * 1e-8) - @test SparseIR.weight_func(K, Fermionic())(482) == 1 - @test SparseIR.weight_func(K_symm, Bosonic())(1e-3) == 1 - @test SparseIR.weight_func(K_symm, Fermionic())(482) == 1 - - K = RegularizedBoseKernel(99) - hints = SparseIR.sve_hints(K, 1e-6) - @test SparseIR.nsvals(hints) == 56 - @test SparseIR.ngauss(hints) == 10 - @test SparseIR.ypower(K) == 1 - @test SparseIR.ypower(SparseIR.get_symmetrized(K, -1)) == 1 - @test SparseIR.ypower(SparseIR.get_symmetrized(K, 1)) == 1 - @test SparseIR.conv_radius(K) == 40 * 99 - @test SparseIR.conv_radius(SparseIR.get_symmetrized(K, -1)) == 40 * 99 - @test SparseIR.conv_radius(SparseIR.get_symmetrized(K, 1)) == 40 * 99 - @test SparseIR.weight_func(K, Bosonic())(482) == 1 / 482 - @test_throws ErrorException SparseIR.weight_func(K, Fermionic()) -end diff --git a/test/poly.jl b/test/poly.jl deleted file mode 100644 index 2fbfd5b..0000000 --- a/test/poly.jl +++ /dev/null @@ -1,401 +0,0 @@ -@testitem "StableRNG" begin - using StableRNGs: StableRNG - - # Useful for porting poly.jl to C++ - # https://github.com/SpM-lab/SparseIR.jl/issues/51 - rng = StableRNG(2024) - data = rand(rng, 3, 3) - knots = rand(rng, size(data, 2) + 1) |> sort - @test data == [0.8177021060277301 0.7085670484724618 0.5033588232863977; - 0.3804323567786363 0.7911959541742282 0.8268504271915096; - 0.5425813266814807 0.38397463704084633 0.21626598379927042] - @test knots == [ - 0.507134318967235, 0.5766150365607372, 0.7126662232433161, 0.7357313003784003 - ] - @assert issorted(knots) - - drng = StableRNG(999) - randsymm = rand(drng, 1:10) - randsymm == 9 - ddata = rand(drng, 3, 3) - ddata == [0.5328437345518631 0.8443074122979211 0.6722336389122814; - 0.1799506228788046 0.6805545318460489 0.17641780726469292; - 0.13124858727993338 0.2193663343416914 0.7756615110113394] -end - -@testitem "PiecewiseLegendrePoly(data::Matrix, knots::Vector, l::Int)" begin - using StableRNGs: StableRNG - - rng = StableRNG(2024) - data = rand(rng, 3, 3) - knots = rand(rng, size(data, 2) + 1) |> sort - l = 3 - - pwlp = SparseIR.PiecewiseLegendrePoly(data, knots, l) - @test pwlp.data == data - @test pwlp.xmin == first(knots) - @test pwlp.xmax == last(knots) - @test pwlp.knots == knots - @test pwlp.polyorder == size(data, 1) - @test pwlp.symm == 0 - - # pwlp(x::Real) - x = 0.5328437345518631 - i, x̃ = SparseIR.split(pwlp, x) - @test i == 1 - @test x̃ ≈ -0.25995538114498773 - ref = SparseIR.legval(x̃, @view SparseIR.data(pwlp)[:, i]) * SparseIR.norms(pwlp)[i] - @test pwlp(x) ≈ ref ≈ 2.696073744825952 - # pwlp(x::AbstractVector) - @test all(pwlp([0.5328437345518631, 0.5328437345518631]) .≈ [ref, ref]) -end - -@testitem "PiecewiseLegendrePoly(data, p::PiecewiseLegendrePoly; symm=symm(p))" begin - using StableRNGs: StableRNG - - rng = StableRNG(2024) - data = rand(rng, 3, 3) - knots = rand(rng, size(data, 2) + 1) |> sort - l = 3 - - pwlp = SparseIR.PiecewiseLegendrePoly(data, knots, l) - - drng = StableRNG(999) - randsymm = rand(drng, Int) - ddata = rand(drng, 3, 3) - ddata_pwlp = SparseIR.PiecewiseLegendrePoly(ddata, pwlp; symm=randsymm) - - @test ddata_pwlp.data == ddata - @test ddata_pwlp.symm == randsymm - for n in fieldnames(SparseIR.PiecewiseLegendrePoly) - n === :data && continue - n === :symm && continue - @test getfield(pwlp, n) == getfield(ddata_pwlp, n) - end -end - -@testsnippet PolyTestData begin - #= - julia> # The following data and knots are generated by - julia> using SparseIR - julia> sve_result = SparseIR.SVEResult(LogisticKernel(1.0)) - julia> data1 = sve_result.u[1].data - julia> data2 = sve_result.u[2].data - julia> data3 = sve_result.u[3].data - julia> knots1 = sve_result.u[1].knots - julia> knots2 = sve_result.u[2].knots - julia> knots3 = sve_result.u[3].knots - julia> l1 = sve_result.u[1].l - julia> l2 = sve_result.u[2].l - julia> l3 = sve_result.u[3].l - =# - - data1 = reshape( - [0.49996553669802485, -0.009838135710548356, 0.003315915376286483, - -2.4035906967802686e-5, 3.4824832610792906e-6, -1.6818592059096e-8, - 1.5530850593697272e-9, -5.67191158452736e-12, 3.8438802553084145e-13, - -1.12861464373688e-15, -1.4028528586225198e-16, 5.199431653846204e-18, - -3.490774002228127e-16, 4.339342349553959e-18, -8.247505551908268e-17, - 7.379549188001237e-19, 0.49996553669802485, 0.009838135710548356, - 0.003315915376286483, 2.4035906967802686e-5, 3.4824832610792906e-6, - 1.6818592059096e-8, 1.5530850593697272e-9, 5.67191158452736e-12, - 3.8438802553084145e-13, 1.12861464373688e-15, -1.4028528586225198e-16, - -5.199431653846204e-18, -3.490774002228127e-16, -4.339342349553959e-18, - -8.247505551908268e-17, -7.379549188001237e-19], - 16, - 2 - ) - - knots1 = [-1.0, 0.0, 1.0] - l1 = 0 - - data2 = reshape( - [-0.43195475509329695, 0.436151579050162, -0.005257007544885257, - 0.0010660519696441624, -6.611545612452212e-6, 7.461310619506964e-7, - -3.2179499894475862e-9, 2.5166526274315926e-10, -8.387341925898803e-13, - 5.008268649326024e-14, 3.7750894390998034e-17, -2.304983535459561e-16, - 3.0252856483620636e-16, -1.923751082183687e-16, 7.201014354168769e-17, - -3.2715804561902326e-17, 0.43195475509329695, 0.436151579050162, - 0.005257007544885257, 0.0010660519696441624, 6.611545612452212e-6, - 7.461310619506964e-7, 3.2179499894475862e-9, 2.5166526274315926e-10, - 8.387341925898803e-13, 5.008268649326024e-14, -3.7750894390998034e-17, - -2.304983535459561e-16, -3.0252856483620636e-16, -1.923751082183687e-16, - -7.201014354168769e-17, -3.2715804561902326e-17], - 16, - 2 - ) - - knots2 = [-1.0, 0.0, 1.0] - l2 = 1 - - data3 = reshape( - [-0.005870438661638806, -0.8376202388555938, 0.28368166184926036, - -0.0029450618222246236, 0.0004277118923277169, -2.4101642603229184e-6, - 2.2287962786878678e-7, -8.875091544426018e-10, 6.021488924175155e-11, - -1.8705305570705647e-13, 9.924398482443944e-15, 4.299521053905097e-16, - -1.0697019178666955e-16, 3.6972269778329906e-16, - -8.848885164903329e-17, 6.327687614609368e-17, -0.005870438661638806, - 0.8376202388555938, 0.28368166184926036, 0.0029450618222246236, - 0.0004277118923277169, 2.4101642603229184e-6, 2.2287962786878678e-7, - 8.875091544426018e-10, 6.021488924175155e-11, 1.8705305570705647e-13, - 9.924398482443944e-15, -4.299521053905097e-16, -1.0697019178666955e-16, - -3.6972269778329906e-16, -8.848885164903329e-17, -6.327687614609368e-17], - 16, - 2 - ) - - knots3 = [-1.0, 0.0, 1.0] - l3 = 2 - - pwlp1 = SparseIR.PiecewiseLegendrePoly(data1, knots1, l1) - pwlp2 = SparseIR.PiecewiseLegendrePoly(data2, knots2, l2) - pwlp3 = SparseIR.PiecewiseLegendrePoly(data3, knots3, l3) - - # polys = SparseIR.PiecewiseLegendrePolyVector([pwlp1, pwlp2, pwlp3]) - - polys = SparseIR.PiecewiseLegendrePolyVector( - [SparseIR.PiecewiseLegendrePoly(data1, knots1, l1) - SparseIR.PiecewiseLegendrePoly(data2, knots2, l2) - SparseIR.PiecewiseLegendrePoly(data3, knots3, l3)] - ) -end - -@testitem "PiecewiseLegendrePolyVector datacheck" setup=[PolyTestData] begin - @test length(polys) == 3 - @test SparseIR.xmin(polys) == SparseIR.xmin(pwlp1) - @test SparseIR.xmax(polys) == SparseIR.xmax(pwlp1) - @test SparseIR.knots(polys) == SparseIR.knots(pwlp1) - @test SparseIR.Δx(polys) == SparseIR.Δx(pwlp1) - @test SparseIR.polyorder(polys) == SparseIR.polyorder(pwlp1) - @test SparseIR.norms(polys) == SparseIR.norms(pwlp1) - @test SparseIR.symm(polys) == SparseIR.symm.([pwlp1, pwlp2, pwlp3]) -end - -@testitem "polys(x::Float64)" setup=[PolyTestData] begin - using StableRNGs: StableRNG - - x = rand(StableRNG(42)) - @test polys(x) == [pwlp1(x), pwlp2(x), pwlp3(x)] - @test SparseIR.data(polys) == - cat(SparseIR.data.([pwlp1, pwlp2, pwlp3])...; dims=3) -end - -@testitem "polys(x::Array)" setup=[PolyTestData] begin - using StableRNGs: StableRNG - - x = rand(StableRNG(42), 2, 1, 4) - tar = polys(x) - @test size(tar) == (3, 2, 1, 4) - ref = reshape(vcat([[pwlp1(e), pwlp2(e), pwlp3(e)] for e in x]...), 3, 2, 1, 4) - @test tar == ref -end - -@testitem "PiecewiseLegendrePolyVector(polys::PiecewiseLegendrePolyVector, knots::AbstractVector)" setup=[PolyTestData] begin - new_knots = [-1.0, 0.0, 1.0] - - new_polys = SparseIR.PiecewiseLegendrePolyVector( - polys, new_knots; - symm=zeros(Int, size(SparseIR.data(polys), 3)) - ) - - @test SparseIR.data(new_polys) == SparseIR.data(polys) - @test SparseIR.knots(new_polys) == SparseIR.knots(new_polys) - @test SparseIR.Δx(new_polys) == diff(new_knots) -end - -@testitem "deriv" begin - using StableRNGs: StableRNG - - # independent from sve.jl - # https://github.com/SpM-lab/SparseIR.jl/issues/51 - rng = StableRNG(2024) - - data = rand(rng, 3, 3) - knots = rand(rng, size(data, 2) + 1) |> sort - l = 3 - pwlp = SparseIR.PiecewiseLegendrePoly(data, knots, l) - - n = 1 - ddata = SparseIR.legder(pwlp.data, n) - ddata .*= pwlp.inv_xs' - - deriv_pwlp = SparseIR.deriv(pwlp) - - @test deriv_pwlp.data == ddata - @test deriv_pwlp.symm == 0 - - for n in fieldnames(SparseIR.PiecewiseLegendrePoly) - n === :data && continue - n === :symm && continue - @test getfield(pwlp, n) == getfield(deriv_pwlp, n) - end -end - -@testitem "shape" setup=[CommonTestData] begin - u, s, v = SparseIR.part(CommonTestData.sve_logistic[42]) - l = length(s) - @test size(u) == (l,) - - @test size(u[4]) == () - @test size(u[3:5]) == (3,) -end - -@testitem "knots" setup=[CommonTestData] begin - u, s, v = CommonTestData.sve_logistic[42] - @test first(u[1].knots) == -1.0 - @test last(u[1].knots) == 1.0 -end - -@testitem "slice" setup=[CommonTestData] begin - sve_result = CommonTestData.sve_logistic[42] - - basis = FiniteTempBasis{Fermionic}(4.2, 10.0; sve_result) - @test length(basis[1:4]) == 4 -end - -@testitem "eval" setup=[CommonTestData] begin - u, s, v = SparseIR.part(CommonTestData.sve_logistic[42]) - l = length(s) - - # Evaluate - @test u(0.4) == [u[i](0.4) for i in 1:l] - @test u.([0.4, -0.2]) == [[u[i](x) for i in 1:l] for x in (0.4, -0.2)] -end - -@testitem "matrix_hat" setup=[CommonTestData] begin - u, s, v = SparseIR.part(CommonTestData.sve_logistic[42]) - uhat = SparseIR.PiecewiseLegendreFTVector(u, Fermionic()) - - n = MatsubaraFreq.([1, 3, 5, -1, -3, 5]) - result1 = uhat[1](n) - result = uhat(reshape(n, (3, 2))) - result_iter = reshape(uhat(n), (:, 3, 2)) - @test size(result1) == (length(n),) - @test size(result) == size(result_iter) - @test result == result_iter -end - -@testitem "overlap" setup=[CommonTestData] begin - for Λ in (10, 42, 10_000) - atol = 1e-13 - u, s, v = SparseIR.part(CommonTestData.sve_logistic[Λ]) - - # Keep only even number of polynomials - u, s, v = u[1:(end - end % 2)], s[1:(end - end % 2)], v[1:(end - end % 2)] - - @test overlap(u[1], u[1])≈1 rtol=0 atol=atol - @test overlap(u[1], u[2])≈0 rtol=0 atol=atol - @test overlap(u[1], u[1]; points=[0.1, 0.2])≈1 rtol=0 atol=atol - - ref = float.(eachindex(s) .== 1) - @test all(isapprox.(overlap(u[1], u), ref; rtol=0, atol)) - end -end - -@testitem "overlap(poly::PiecewiseLegendrePoly, f::F)" begin - using StableRNGs: StableRNG - - # independent from sve.jl - # https://github.com/SpM-lab/SparseIR.jl/issues/51 - rng = StableRNG(2024) - - data = rand(rng, 3, 3) - knots = rand(rng, size(data, 2) + 1) |> sort - l = 3 - pwlp = SparseIR.PiecewiseLegendrePoly(data, knots, l) - - if Sys.isapple() && Sys.ARCH === :aarch64 - # On macOS (arm64-apple-darwin22.4.0), we get - ∫pwlp, ∫pwlp_err = (0.4934184996836404, 8.326672684688674e-17) - else - ∫pwlp, ∫pwlp_err = (0.4934184996836403, 2.7755575615628914e-17) - end - - @test overlap(pwlp, identity) ≈ ∫pwlp - @test all(overlap(pwlp, identity; return_error=true) .≈ (∫pwlp, ∫pwlp_err)) -end - -@testitem "roots(poly::PiecewiseLegendrePoly; tol=1e-10, alpha=Val(2))" begin - # https://github.com/SpM-lab/SparseIR.jl/issues/51 - #= - The following data and knots are generated by - julia> using SparseIR - julia> using Test - julia> Λ = 1.0 - julia> sve_result = SparseIR.SVEResult(SparseIR.LogisticKernel(Λ)) - julia> basis = SparseIR.FiniteTempBasis{SparseIR.Fermionic}(1, Λ; sve_result) - =# - - data = reshape( - [0.16774734206553019, 0.49223680914312595, -0.8276728567928646, - 0.16912891046582143, -0.0016231275318572044, 0.00018381683946452256, - -9.699355027805034e-7, 7.60144228530804e-8, -2.8518324490258146e-10, - 1.7090590205708293e-11, -5.0081401126025e-14, 2.1244236198427895e-15, - 2.0478095258000225e-16, -2.676573801530628e-16, 2.338165820094204e-16, - -1.2050663212312096e-16, -0.16774734206553019, 0.49223680914312595, - 0.8276728567928646, 0.16912891046582143, 0.0016231275318572044, - 0.00018381683946452256, 9.699355027805034e-7, 7.60144228530804e-8, - 2.8518324490258146e-10, 1.7090590205708293e-11, 5.0081401126025e-14, - 2.1244236198427895e-15, -2.0478095258000225e-16, -2.676573801530628e-16, - -2.338165820094204e-16, -1.2050663212312096e-16], - 16, - 2) - - knots = [0.0, 0.5, 1.0] - l = 3 - - # expected behavior - #= - julia> @test basis.u[4].data == data - julia> @test basis.u[4].knots == knots - julia> @test basis.u[4].l == l - =# - - pwlp = SparseIR.PiecewiseLegendrePoly(data, knots, l) - @test SparseIR.roots(pwlp) == [0.1118633448586015 - 0.4999999999999998 - 0.8881366551413985] -end - -@testitem "eval unique" setup=[CommonTestData] begin - u, s, v = SparseIR.part(CommonTestData.sve_logistic[42]) - û = SparseIR.PiecewiseLegendreFTVector(u, Fermionic()) - - # evaluate - res1 = û([1, 3, 3, 1]) - idx = [1, 2, 2, 1] - res2 = û([1, 3])[:, idx] - @test res1 == res2 -end - -@testitem "unit tests" setup=[CommonTestData] begin - u, s, v = SparseIR.part(CommonTestData.sve_logistic[42]) - - @test size(u[1](rand(30))) == (30,) - - @test_throws DomainError u(SparseIR.xmax(u) + 123) - @test_throws DomainError u(SparseIR.xmin(u) - 123) - - int_result, int_error = SparseIR.overlap(u[1], u[1]; return_error=true) - - @test int_error < eps() - - u_linearcombination = 2u[1] - 3u[2] - @test SparseIR.overlap(u_linearcombination, u[2]) ≈ -3 - - @test size(u(rand(2, 3, 4))) == (length(u), 2, 3, 4) - - @test (SparseIR.xmin(u), SparseIR.xmax(u)) === (-1.0, 1.0) - @test SparseIR.knots(u) == u[end].knots - @test SparseIR.Δx(u) == u[1].Δx - @test SparseIR.symm(u)[2] == SparseIR.symm(u[2]) - @test all(<(eps()), SparseIR.overlap(u, sin)[1:2:end]) - @test all(<(eps()), SparseIR.overlap(u, cos)[2:2:end]) - - û = SparseIR.PiecewiseLegendreFTVector(u, Fermionic()) - - @test length(SparseIR.moments(û)[1]) == length(û) - û_weird = SparseIR.PiecewiseLegendreFT(-9u[2] + u[3], Fermionic()) - @test_throws ErrorException SparseIR.func_for_part(û_weird) - @test SparseIR.phase_stable(u, 5) ≈ SparseIR.phase_stable(u, 5.0) -end diff --git a/test/runtests.jl b/test/runtests.jl index 5dc519c..76d65a6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,5 @@ -using TestItemRunner +using SparseIR +using ReTestItems -if v"1.11" <= VERSION < v"1.13-" - @run_package_tests -else - @run_package_tests filter = ti -> !(:jet_tests in ti.tags) -end +# Run all test files +runtests(SparseIR; tags=[:julia]) diff --git a/test/sampling.jl b/test/sampling.jl deleted file mode 100644 index 8e195d1..0000000 --- a/test/sampling.jl +++ /dev/null @@ -1,207 +0,0 @@ -@testitem "alias" setup=[CommonTestData] begin - β = 1 - ωmax = 10 - basis = FiniteTempBasis{Fermionic}( - β, ωmax; sve_result=CommonTestData.sve_logistic[β * ωmax]) - @test TauSampling(basis) isa TauSampling64 -end - -@testitem "decomp" begin - using Random - using LinearAlgebra - - Random.seed!(420) - A = randn(49, 39) - - Ad = svd(A) - norm_A = first(Ad.S) / last(Ad.S) - @test all(isapprox.(A, Matrix(Ad), atol=1e-15 * norm_A, rtol=0)) - - x = randn(39) - @test A * x≈Ad.U * Diagonal(Ad.S) * Ad.Vt * x atol=1e-14 * norm_A rtol=0 - - x = randn(39, 3) - @test A * x≈Ad.U * Diagonal(Ad.S) * Ad.Vt * x atol=2e-14 * norm_A rtol=0 - - y = randn(49) - @test A \ y≈Ad \ y atol=1e-14 * norm_A rtol=0 - - y = randn(49, 2) - @test A \ y≈Ad \ y atol=1e-14 * norm_A rtol=0 -end - -@testitem "don't factorize" setup=[CommonTestData] begin - stat = Bosonic() - Λ = 10 - basis = FiniteTempBasis(stat, 1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - τ_smpl = TauSampling(basis; factorize=false) - ω_smpl = MatsubaraSampling(basis; factorize=false) - @test isnothing(τ_smpl.matrix_svd) - @test isnothing(ω_smpl.matrix_svd) -end - -@testitem "fit from tau" setup=[CommonTestData] begin - using Random - - for stat in (Bosonic(), Fermionic()), Λ in (10, 42) - basis = FiniteTempBasis(stat, 1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - smpl = TauSampling(basis) - @test issorted(smpl.sampling_points) - Random.seed!(5318008) - - shape = (2, 3, 4) - rhol = randn(ComplexF64, (length(basis), shape...)) - originalgl = -basis.s .* rhol - for dim in 1:ndims(rhol) - gl = SparseIR.movedim(originalgl, 1 => dim) - gtau = evaluate(smpl, gl; dim) - @test size(gtau) == (size(gl)[1:(dim - 1)]..., - length(smpl.sampling_points), - size(gl)[(dim + 1):end]...) - - gl_from_tau = fit(smpl, gtau; dim) - @test gl_from_tau ≈ gl - - gl_from_tau2 = similar(gl_from_tau) - fit!(gl_from_tau2, smpl, gtau; dim) - @test gl_from_tau2 ≈ gl - end - end -end - -@testitem "τ noise" setup=[CommonTestData] begin - using Random - using LinearAlgebra - - for stat in (Bosonic(), Fermionic()), Λ in (10, 42) - basis = FiniteTempBasis(stat, 1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - smpl = TauSampling(basis) - @test basis === SparseIR.basis(smpl) - @test issorted(smpl.sampling_points) - Random.seed!(5318008) - - ρℓ = basis.v([-0.999, -0.01, 0.5]) * [0.8, -0.2, 0.5] - Gℓ = basis.s .* ρℓ - Gℓ_magn = norm(Gℓ) - @inferred evaluate(smpl, Gℓ) - @inferred evaluate(smpl, Gℓ, dim=1) - Gτ = evaluate(smpl, Gℓ) - - Gτ_inplace = similar(Gτ) - evaluate!(Gτ_inplace, smpl, Gℓ) - @test Gτ == Gτ_inplace - - noise = 1e-5 - Gτ_n = Gτ + noise * norm(Gτ) * randn(size(Gτ)...) - @inferred fit(smpl, Gτ_n) - @inferred fit(smpl, Gτ_n, dim=1) - Gℓ_n = fit(smpl, Gτ_n) - - Gℓ_n_inplace = similar(Gℓ_n) - fit!(Gℓ_n_inplace, smpl, Gτ_n) - @test Gℓ_n == Gℓ_n_inplace - - @test isapprox(Gℓ, Gℓ_n, atol=12 * noise * Gℓ_magn, rtol=0) - end -end - -@testitem "iω noise" setup=[CommonTestData] begin - using Random - using LinearAlgebra - - for stat in (Bosonic(), Fermionic()), Λ in (10, 42), positive_only in (false, true) - basis = FiniteTempBasis(stat, 1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - smpl = MatsubaraSampling(basis; positive_only) - @test basis === SparseIR.basis(smpl) - if !positive_only - @test smpl isa - (stat == Fermionic() ? MatsubaraSampling64F : MatsubaraSampling64B) - end - @test issorted(smpl.sampling_points) - Random.seed!(1312 + 161) - - ρℓ = basis.v([-0.999, -0.01, 0.5]) * [0.8, -0.2, 0.5] - Gℓ = basis.s .* ρℓ - Gℓ_magn = norm(Gℓ) - @inferred evaluate(smpl, Gℓ) - @inferred evaluate(smpl, Gℓ, dim=1) - Giw = evaluate(smpl, Gℓ) - - Giw_inplace = similar(Giw) - evaluate!(Giw_inplace, smpl, Gℓ) - @test Giw == Giw_inplace - - noise = 1e-5 - Giwn_n = Giw + noise * norm(Giw) * randn(size(Giw)...) - @inferred fit(smpl, Giwn_n) - @inferred fit(smpl, Giwn_n, dim=1) - Gℓ_n = fit(smpl, Giwn_n) - @test isapprox(Gℓ, Gℓ_n, atol=40 * sqrt(1 + positive_only) * noise * Gℓ_magn, - rtol=0) - - Gℓ_n_inplace = similar(Gℓ_n) - fit!(Gℓ_n_inplace, smpl, Giwn_n) - @test Gℓ_n == Gℓ_n_inplace - end -end - -@testitem "conditioning" begin - using LinearAlgebra - - basis = FiniteTempBasis{Fermionic}(3, 3, 1e-6) - @test cond(TauSampling(basis)) < 3 - @test cond(MatsubaraSampling(basis)) < 5 - @test_logs (:warn, - r"Sampling matrix is poorly conditioned \(cond = \d\.\d+e\d+\)\.") TauSampling( - basis; - sampling_points=[ - 1.0, - 1.0 - ]) - @test_logs (:warn, - r"Sampling matrix is poorly conditioned \(cond = \d\.\d+e\d+\)\.") MatsubaraSampling( - basis; - sampling_points=[ - FermionicFreq(1), - FermionicFreq(1) - ]) - - basis = FiniteTempBasis{Fermionic}(3, 3, 1e-2) - @test cond(TauSampling(basis)) < 2 - @test cond(MatsubaraSampling(basis)) < 3 -end - -@testitem "errors" begin - for stat in (Bosonic(), Fermionic()), sampling in (TauSampling, MatsubaraSampling) - basis = FiniteTempBasis(stat, 3, 3, 1e-6) - smpl = sampling(basis) - @test_throws DimensionMismatch evaluate(smpl, rand(100)) - @test_throws DimensionMismatch evaluate!(rand(100), smpl, rand(100)) - @test_throws DimensionMismatch fit(smpl, rand(100)) - @test_throws DimensionMismatch fit!(rand(100), smpl, rand(100)) - @test_throws DomainError SparseIR.matop!(rand(2, 3, 4), rand(5, 6), rand(7, 8, 9), - *, 2) - end -end - -@testitem "noalloc divs" begin - using LinearAlgebra - - A = rand(ComplexF64, 3, 4) - B = rand(ComplexF64, 4, 5) - B_SVD = svd(B) - Y = Matrix{ComplexF64}(undef, 3, 5) - workarr = Vector{ComplexF64}(undef, 4 * 5) - - SparseIR.rdiv_noalloc!(Y, A, B_SVD, workarr) - - @test Y * transpose(B) ≈ A -end - -@testitem "frequency range" begin - basis = FiniteTempBasis{Fermionic}(3, 3, 1e-6) - freqrange = SparseIR.frequency_range(12) - smpl = MatsubaraSampling(basis; sampling_points=freqrange) - @test sampling_points(smpl) !== freqrange - @test sampling_points(smpl) == freqrange -end diff --git a/test/spir/augment_tests.jl b/test/spir/augment_tests.jl new file mode 100644 index 0000000..ab1bec9 --- /dev/null +++ b/test/spir/augment_tests.jl @@ -0,0 +1,120 @@ + +@testitem "augment.jl" tags=[:julia, :sparseir, :debug] begin + using Test + using SparseIR + import SparseIR as SparseIR + using LinearAlgebra + using StableRNGs + + @testset "Augmented bosonic basis" begin + ωmax = 2 + β = 1000 + basis = FiniteTempBasis{Bosonic}(β, ωmax, 1e-6) + basis_aug = AugmentedBasis(basis, TauConst, TauLinear) + + @test all(isone, SparseIR.significance(basis_aug)[1:3]) + rng = StableRNG(42) + + gτ = rand(rng, length(basis_aug)) + τ_smpl = TauSampling(basis_aug) + gl_fit = fit(τ_smpl, gτ) + gτ_reconst = evaluate(τ_smpl, gl_fit) + + @test size(gτ_reconst) == size(gτ) + + @test isapprox(gτ_reconst, gτ, atol=1e-8) + end + + @testset "Vertex basis with stat = $stat" for stat in (Fermionic(), Bosonic()) + ωmax = 2 + β = 1000 + basis = FiniteTempBasis(stat, β, ωmax, 1e-6) + basis_aug = AugmentedBasis(basis, MatsubaraConst) + @test !isnothing(basis_aug.uhat) + + # G(iν) = c + 1 / (iν - pole) + pole = 1.0 + c = 1.0 + matsu_smpl = MatsubaraSampling(basis_aug) + giν = @. c + 1 / (SparseIR.valueim(matsu_smpl.ωn, β) - pole) + gl = fit(matsu_smpl, giν) + + giν_reconst = evaluate(matsu_smpl, gl) + + @test isapprox(giν_reconst, giν, atol=maximum(abs, giν) * 1e-7) + end + + @testset "unit tests" begin + β = 1000 + ωmax = 2 + basis = FiniteTempBasis{Bosonic}(β, ωmax, 1e-6) + basis_aug = AugmentedBasis(basis, TauConst, TauLinear) + + @testset "getindex" begin + @test length(basis_aug.u[1:5]) == 5 + @test_throws ErrorException basis_aug.u[1:2] + @test_throws ErrorException basis_aug.u[3:7] + @test basis_aug.u[1] isa TauConst + @test basis_aug.u[2] isa TauLinear + end + + len_basis = length(basis) + len_aug = len_basis + 2 + + @test size(basis_aug) == (len_aug,) + @test SparseIR.accuracy(basis_aug) == SparseIR.accuracy(basis) + @test SparseIR.Λ(basis_aug) == β * ωmax + @test SparseIR.ωmax(basis_aug) == ωmax + + @test size(basis_aug.u) == (len_aug,) + @test length(basis_aug.u(0.8)) == len_aug + + @testset "create" begin + @test SparseIR.create(MatsubaraConst(42), basis) == MatsubaraConst(42) + @test SparseIR.create(MatsubaraConst, basis) == MatsubaraConst(β) + end + + @testset "TauConst" begin + @test_throws DomainError TauConst(-34) + tc = TauConst(123) + @test SparseIR.β(tc) == 123.0 + @test_throws DomainError tc(-123) + @test_throws DomainError tc(321) + @test tc(50) == 1 / sqrt(123) + @test tc(MatsubaraFreq(0)) == sqrt(123) + @test tc(MatsubaraFreq(92)) == 0.0 + @test_throws ErrorException tc(MatsubaraFreq(93)) + @test SparseIR.deriv(tc)(4.2) == 0.0 + @test SparseIR.deriv(tc, Val(0)) == tc + end + + @testset "TauLinear" begin + @test_throws DomainError TauLinear(-34) + tl = TauLinear(123) + @test SparseIR.β(tl) == 123.0 + @test_throws DomainError tl(-1003) + @test_throws DomainError tl(1003) + @test tl(50) ≈ sqrt(3 / 123) * (2 / 123 * 50 - 1) + @test tl(MatsubaraFreq(0)) == 0.0 + @test tl(MatsubaraFreq(92)) ≈ sqrt(3 / 123) * 2 / im * 123 / (92 * π) + @test_throws ErrorException tl(MatsubaraFreq(93)) + @test SparseIR.deriv(tl, Val(0)) == tl + @test SparseIR.deriv(tl)(4.2) ≈ sqrt(3 / 123) * 2 / 123 + @test SparseIR.deriv(tl, Val(2))(4.2) == 0.0 + end + + @testset "MatsubaraConst" begin + @test_throws DomainError MatsubaraConst(-34) + mc = MatsubaraConst(123) + @test SparseIR.β(mc) == 123.0 + @test_throws DomainError mc(-123) + @test_throws DomainError mc(321) + @test isnan(mc(30)) + @test mc(MatsubaraFreq(0)) == 1.0 + @test mc(MatsubaraFreq(92)) == 1.0 + @test mc(MatsubaraFreq(93)) == 1.0 + @test SparseIR.deriv(mc) == mc + @test SparseIR.deriv(mc, Val(0)) == mc + end + end +end diff --git a/test/spir/basis_set_tests.jl b/test/spir/basis_set_tests.jl new file mode 100644 index 0000000..7940a59 --- /dev/null +++ b/test/spir/basis_set_tests.jl @@ -0,0 +1,56 @@ +@testitem "basis_set.jl" tags=[:julia, :spir] begin + using Test + using SparseIR + import SparseIR as SparseIR + + @testset "consistency" begin + β = 2.0 + ωmax = 5.0 + ε = 1e-5 + basis_f, basis_b = SparseIR.finite_temp_bases(β, ωmax, ε) + bs = FiniteTempBasisSet(β, ωmax, ε) + + @test length(bs.basis_f) == length(basis_f) + @test length(bs.basis_b) == length(basis_b) + end + + @testset "consistency2" begin + β = 2.0 + ωmax = 5.0 + ε = 1e-5 + + sve_result = SparseIR.SVEResult(LogisticKernel(β * ωmax), ε) + basis_f, basis_b = SparseIR.finite_temp_bases(β, ωmax, ε; sve_result) + smpl_τ_f = TauSampling(basis_f) + smpl_τ_b = TauSampling(basis_b) + smpl_wn_f = MatsubaraSampling(basis_f) + smpl_wn_b = MatsubaraSampling(basis_b) + + bs = FiniteTempBasisSet(β, ωmax, ε; sve_result) + @test smpl_τ_f.sampling_points == smpl_τ_b.sampling_points + #@test bs.smpl_tau_f.matrix == smpl_τ_f.matrix + #@test bs.smpl_tau_b.matrix == smpl_τ_b.matrix + + #@test bs.smpl_wn_f.matrix == smpl_wn_f.matrix + #@test bs.smpl_wn_b.matrix == smpl_wn_b.matrix + end + + @testset "unit tests LogisticKernel" begin + β = 23 + ωmax = 3e-2 + ε = 1e-5 + + bset = FiniteTempBasisSet(β, ωmax, ε) + + basis_f = FiniteTempBasis{Fermionic}(β, ωmax, ε) + basis_b = FiniteTempBasis{Bosonic}(β, ωmax, ε) + @test SparseIR.β(bset) == β + @test SparseIR.ωmax(bset) == ωmax + @test bset.tau == SparseIR.sampling_points(TauSampling(basis_f)) + @test bset.wn_f == SparseIR.sampling_points(MatsubaraSampling(basis_f)) + @test bset.wn_b == SparseIR.sampling_points(MatsubaraSampling(basis_b)) + # @test bset.sve_result.s ≈ SparseIR.SVEResult(LogisticKernel(β * ωmax); ε).s + @test :tau ∈ propertynames(bset) + # SparseIR.finite_temp_bases(0.1, 0.2, 1e-3; kernel=RegularizedBoseKernel(0.1 * 0.2)) + end +end # testitem diff --git a/test/spir/basis_tests.jl b/test/spir/basis_tests.jl new file mode 100644 index 0000000..376655e --- /dev/null +++ b/test/spir/basis_tests.jl @@ -0,0 +1,27 @@ +@testitem "basis.jl" tags=[:julia, :lib] begin + using SparseIR + + β = 2.0 + ωmax = 5.0 + ε = 1e-6 + Λ = β * ωmax + @testset "FiniteTempBasis{S} for S=$(S)" for S in [Fermionic, Bosonic] + basis = FiniteTempBasis(S(), β, ωmax, ε) + @test true + end + + @testset "FiniteTempBasis{S} for S=$(S)" for S in [Fermionic, Bosonic] + kernel = LogisticKernel(10.0) + basis = FiniteTempBasis(S(), β, ωmax, ε; kernel) + @test true + end + + @testset "FiniteTempBasis{S} for K=RegularizedBoseKernel" begin + kernel = RegularizedBoseKernel(10.0) + @test_throws ArgumentError("RegularizedBoseKernel is incompatible with Fermionic statistics") FiniteTempBasis( + Fermionic(), β, ωmax, ε; kernel) + + kernel = RegularizedBoseKernel(10.0) + basis = FiniteTempBasis(Bosonic(), β, ωmax, ε; kernel) + end +end diff --git a/test/spir/dlr_tests.jl b/test/spir/dlr_tests.jl new file mode 100644 index 0000000..468539b --- /dev/null +++ b/test/spir/dlr_tests.jl @@ -0,0 +1,367 @@ +@testitem "DLR Constructor" tags=[:julia, :spir] begin + using SparseIR + using Test + using Random + using LinearAlgebra + + @testset "Constructor with default poles" begin + # Test with Fermionic statistics + β = 10000.0 + ωmax = 1.0 + ε = 1e-12 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + @test dlr isa DiscreteLehmannRepresentation + @test dlr isa SparseIR.AbstractBasis + @test SparseIR.statistics(dlr) isa Fermionic + @test SparseIR.β(dlr) == β + @test SparseIR.ωmax(dlr) == ωmax + @test SparseIR.Λ(dlr) == β * ωmax + @test SparseIR.accuracy(dlr) ≤ ε + @test length(dlr) == length(dlr.poles) + @test size(dlr) == (length(dlr),) + @test npoles(dlr) == length(dlr.poles) + @test get_poles(dlr) ≈ dlr.poles + @test all(isone, SparseIR.significance(dlr)) + @test !SparseIR.iswellconditioned(dlr) + + # Test that poles are the default omega sampling points + default_poles = default_omega_sampling_points(basis) + @test dlr.poles ≈ default_poles + end + + @testset "Constructor with custom poles" begin + # Test with Bosonic statistics + β = 10000.0 + ωmax = 1.0 + ε = 1e-12 + + basis = FiniteTempBasis(Bosonic(), β, ωmax, ε) + + # Get default poles and use them as custom poles + default_poles = default_omega_sampling_points(basis) + dlr_custom = DiscreteLehmannRepresentation(basis, default_poles) + + @test dlr_custom isa DiscreteLehmannRepresentation + @test SparseIR.statistics(dlr_custom) isa Bosonic + @test dlr_custom.poles ≈ default_poles + @test npoles(dlr_custom) == length(default_poles) + @test get_poles(dlr_custom) ≈ default_poles + + # Verify poles can be retrieved correctly (like C++ test) + poles_reconst = get_poles(dlr_custom) + @test length(poles_reconst) == length(default_poles) + for i in 1:length(poles_reconst) + @test poles_reconst[i] ≈ default_poles[i] + end + + # Test with random poles + Random.seed!(123) + num_poles = 10 + random_poles = ωmax * (2rand(num_poles) .- 1) + dlr_random = DiscreteLehmannRepresentation(basis, random_poles) + + @test length(dlr_random) == num_poles + @test dlr_random.poles ≈ random_poles + @test maximum(abs, dlr_random.poles) ≤ ωmax + + # Verify custom poles are stored correctly + retrieved_poles = get_poles(dlr_random) + @test retrieved_poles ≈ random_poles + end +end + +@testitem "IR to DLR transformation" tags=[:julia, :spir] begin + using SparseIR + using Test + using Random + using LinearAlgebra + + @testset "Real coefficients - $stat" for stat in (Fermionic(), Bosonic()) + β = 10000.0 + ωmax = 1.0 + ε = 1e-12 + + basis = FiniteTempBasis(stat, β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + # Test 1D arrays + Random.seed!(42) + gl = randn(length(basis)) + + # Transform to DLR + g_dlr = from_IR(dlr, gl) + @test length(g_dlr) == length(dlr) + @test eltype(g_dlr) == Float64 + + # Transform back to IR + gl_reconst = to_IR(dlr, g_dlr) + @test length(gl_reconst) == length(basis) + @test eltype(gl_reconst) == Float64 + + # Since DLR is a different representation, we don't expect exact recovery + # but the error should be reasonable + @test norm(gl_reconst - gl) / norm(gl) < 0.1 # Within 10% relative error + end + + @testset "Complex coefficients" begin + β = 100.0 + ωmax = 10.0 + ε = 1e-10 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + Random.seed!(123) + gl_complex = randn(ComplexF64, length(basis)) + + # Transform to DLR + g_dlr_complex = from_IR(dlr, gl_complex) + @test length(g_dlr_complex) == length(dlr) + @test eltype(g_dlr_complex) == ComplexF64 + + # Transform back + gl_complex_reconst = to_IR(dlr, g_dlr_complex) + @test length(gl_complex_reconst) == length(basis) + @test eltype(gl_complex_reconst) == ComplexF64 + + # Check reasonable reconstruction + @test norm(gl_complex_reconst - gl_complex) / norm(gl_complex) < 0.1 + end + + @testset "Multi-dimensional arrays" begin + β = 100.0 + ωmax = 10.0 + ε = 1e-10 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + # Test 2D array transformations + Random.seed!(42) + gl_2d = randn(length(basis), 3) + + # Transform to DLR + g_dlr_2d = from_IR(dlr, gl_2d) + @test size(g_dlr_2d) == (length(dlr), 3) + + # Transform back to IR + gl_2d_reconst = to_IR(dlr, g_dlr_2d) + @test size(gl_2d_reconst) == (length(basis), 3) + + # Check reconstruction error + @test norm(gl_2d_reconst - gl_2d) / norm(gl_2d) < 0.1 + + # Test with different dimension + gl_2d_dim2 = randn(5, length(basis)) + g_dlr_2d_dim2 = from_IR(dlr, gl_2d_dim2, 2) + @test size(g_dlr_2d_dim2) == (5, length(dlr)) + + gl_2d_dim2_reconst = to_IR(dlr, g_dlr_2d_dim2, 2) + @test size(gl_2d_dim2_reconst) == (5, length(basis)) + @test norm(gl_2d_dim2_reconst - gl_2d_dim2) / norm(gl_2d_dim2) < 0.1 + + # Test 3D array transformations + gl_3d = randn(length(basis), 2, 4) + g_dlr_3d = from_IR(dlr, gl_3d) + @test size(g_dlr_3d) == (length(dlr), 2, 4) + + gl_3d_reconst = to_IR(dlr, g_dlr_3d) + @test size(gl_3d_reconst) == (length(basis), 2, 4) + @test norm(gl_3d_reconst - gl_3d) / norm(gl_3d) < 0.1 + + # Test complex arrays + gl_complex_2d = randn(ComplexF64, length(basis), 3) + g_dlr_complex_2d = from_IR(dlr, gl_complex_2d) + @test eltype(g_dlr_complex_2d) == ComplexF64 + @test size(g_dlr_complex_2d) == (length(dlr), 3) + + gl_complex_2d_reconst = to_IR(dlr, g_dlr_complex_2d) + @test norm(gl_complex_2d_reconst - gl_complex_2d) / norm(gl_complex_2d) < 0.1 + end + + @testset "Error handling" begin + β = 100.0 + ωmax = 1.0 + ε = 1e-12 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + # Test dimension mismatch for from_IR + wrong_size_gl = randn(length(basis) + 1) + @test_throws DimensionMismatch from_IR(dlr, wrong_size_gl) + + # Test dimension mismatch for to_IR + wrong_size_dlr = randn(length(dlr) + 1) + @test_throws DimensionMismatch to_IR(dlr, wrong_size_dlr) + + # Test multi-dimensional mismatch + gl_2d = randn(length(basis), 5) + g_dlr_2d = from_IR(dlr, gl_2d, 1) + @test_throws DimensionMismatch from_IR(dlr, gl_2d, 2) # Wrong dimension + @test_throws DimensionMismatch to_IR(dlr, g_dlr_2d, 2) # Wrong dimension + end +end + +@testitem "DLR with sampling" tags=[:julia, :spir] begin + using SparseIR + using Test + using Random + using LinearAlgebra + + @testset "Compression test - $stat" for stat in (Fermionic(), Bosonic()) + β = 10000.0 + ωmax = 1.0 + ε = 1e-12 + + basis = FiniteTempBasis(stat, β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + Random.seed!(982743) + + # Create a function as a sum of poles + num_poles = 10 + poles = ωmax * (2rand(num_poles) .- 1) + coeffs = 2rand(num_poles) .- 1 + @test maximum(abs, poles) ≤ ωmax + + # Create DLR with these specific poles + dlr_test = DiscreteLehmannRepresentation(basis, poles) + + # The coefficients in DLR directly correspond to the pole expansion + # Convert to IR representation + Gl = to_IR(dlr_test, coeffs) + + # Now use default DLR to represent this function + g_dlr = from_IR(dlr, Gl) + + # Comparison on Matsubara frequencies + smpl = MatsubaraSampling(basis) + smpl_for_dlr = MatsubaraSampling(dlr; sampling_points=sampling_points(smpl)) + + giv_ref = evaluate(smpl, Gl) + giv = evaluate(smpl_for_dlr, g_dlr) + + # DLR should represent the function well + @test isapprox(giv, giv_ref; atol=300ε, rtol=0) + + # Comparison on τ + smpl_τ = TauSampling(basis) + gτ = evaluate(smpl_τ, Gl) + + # For DLR, use the same sampling points as the original basis + smpl_τ_for_dlr = TauSampling(dlr; sampling_points=sampling_points(smpl_τ)) + gτ2 = evaluate(smpl_τ_for_dlr, g_dlr) + + @test isapprox(gτ, gτ2; atol=300ε, rtol=0) + end + + @testset "Bosonic pole representation" begin + β = 2.0 + ωmax = 21.0 + ε = 1e-7 + + basis_b = FiniteTempBasis(Bosonic(), β, ωmax, ε) + + # G(iw) = sum_p coeff_p / (iw - omega_p) + coeff = [1.1, 2.0] + ω_p = [2.2, -1.0] + + # Create DLR with these specific poles + sp = DiscreteLehmannRepresentation(basis_b, ω_p) + + # Convert pole coefficients to IR + gl_pole = to_IR(sp, coeff) + + # This should match the basis representation of the same function + # (up to the accuracy of the representation) + @test length(gl_pole) == length(basis_b) + end +end + +@testitem "DLR properties" tags=[:julia, :spir] begin + using SparseIR + using Test + + @testset "Pole management" begin + β = 100.0 + ωmax = 5.0 + ε = 1e-10 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + + # Test default pole selection + default_poles = default_omega_sampling_points(basis) + @test length(default_poles) > 0 + @test all(abs.(default_poles) .<= ωmax) + + # Create DLR and verify poles + dlr = DiscreteLehmannRepresentation(basis) + @test sampling_points(dlr) == dlr.poles + @test npoles(dlr) == length(dlr.poles) + @test length(get_poles(dlr)) == npoles(dlr) + # The C API may return a different number of poles than requested + @test npoles(dlr) > 0 + + # Test with specific number of poles + n_poles_custom = min(20, length(default_poles)) + custom_poles = default_poles[1:n_poles_custom] + dlr_custom = DiscreteLehmannRepresentation(basis, custom_poles) + @test length(dlr_custom) == n_poles_custom + @test npoles(dlr_custom) == n_poles_custom + end + + @testset "Basis properties inheritance" begin + for stat in (Fermionic(), Bosonic()) + β = 1000.0 + ωmax = 10.0 + ε = 1e-11 + + basis = FiniteTempBasis(stat, β, ωmax, ε) + dlr = DiscreteLehmannRepresentation(basis) + + # Check that all properties are correctly inherited + @test SparseIR.statistics(dlr) == stat + @test SparseIR.β(dlr) == β + @test SparseIR.ωmax(dlr) == ωmax + @test SparseIR.Λ(dlr) == β * ωmax + @test SparseIR.accuracy(dlr) ≤ ε + + # DLR-specific properties + @test all(isone, SparseIR.significance(dlr)) + @test !SparseIR.iswellconditioned(dlr) + @test SparseIR.basis(dlr) === basis + end + end + + @testset "Edge cases" begin + # Small basis + β = 10.0 + ωmax = 1.0 + ε = 1e-3 + + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε) + @test length(basis) < 20 # Should be small + + dlr = DiscreteLehmannRepresentation(basis) + # The C API sometimes returns more poles than the basis size for small bases + @test length(dlr) > 0 + + # Test transformation with zeros + gl_zeros = zeros(length(basis)) + g_dlr_zeros = from_IR(dlr, gl_zeros) + @test all(iszero, g_dlr_zeros) + + gl_reconst_zeros = to_IR(dlr, g_dlr_zeros) + @test all(iszero, gl_reconst_zeros) + + # Test transformation with ones + gl_ones = ones(length(basis)) + g_dlr_ones = from_IR(dlr, gl_ones) + gl_reconst_ones = to_IR(dlr, g_dlr_ones) + # Skip norm test - would need LinearAlgebra import + end +end diff --git a/test/spir/freq_tests.jl b/test/spir/freq_tests.jl new file mode 100644 index 0000000..108ca11 --- /dev/null +++ b/test/spir/freq_tests.jl @@ -0,0 +1,86 @@ +@testitem "freq.jl" tags=[:julia, :spir] begin + using SparseIR + @testset "freq" begin + @test SparseIR.zeta(MatsubaraFreq(2)) == 0 + @test SparseIR.zeta(MatsubaraFreq(-5)) == 1 + + @test Int(FermionicFreq(3)) === 3 + @test Int(BosonicFreq(-2)) === -2 + + @test Int(MatsubaraFreq(Int32(4))) === 4 + + @test_throws DomainError FermionicFreq(4) + @test_throws DomainError BosonicFreq(-7) + + @test FermionicFreq(5) < BosonicFreq(6) + @test BosonicFreq(6) >= BosonicFreq(6) + + @test SparseIR.value(pioverbeta, 3) == π / 3 + @test SparseIR.valueim(2 * pioverbeta, 3) == 2im * π / 3 + + @test !iszero(MatsubaraFreq(-3)) + end + + @testset "freqadd" begin + @test +pioverbeta == pioverbeta + @test iszero(pioverbeta - pioverbeta) + + @test pioverbeta + oneunit(pioverbeta) == 2 * pioverbeta + @test Int(4 * pioverbeta) == 4 + @test Int(pioverbeta - 2 * pioverbeta) == -1 + @test iszero(zero(2 * pioverbeta)) + end + + @testset "freqrange" begin + @test length(FermionicFreq(1):FermionicFreq(-3)) == 0 + @test length(FermionicFreq(3):FermionicFreq(200_000_000_001)) == + 100_000_000_000 + + @test collect(BosonicFreq(2):BosonicFreq(-2)) == [] + @test collect(BosonicFreq(0):BosonicFreq(4)) == (0:2:4) .* pioverbeta + @test collect(FermionicFreq(-3):FermionicFreq(1)) == (-3:2:1) .* pioverbeta + + @test length(FermionicFreq(37):BosonicFreq(10):FermionicFreq(87)) == 6 + @test collect(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(58)) == + (-10:4:58) .* pioverbeta + @test collect(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(60)) == + (-10:4:58) .* pioverbeta + @test length(BosonicFreq(-10):BosonicFreq(4):BosonicFreq(60)) == 18 + @test length(FermionicFreq(1):BosonicFreq(100):FermionicFreq(3)) == 1 + @test length(FermionicFreq(1):BosonicFreq(100):FermionicFreq(-1001)) == 0 + + @test_throws MethodError FermionicFreq(5):BosonicFreq(100) + @test_throws MethodError BosonicFreq(6):FermionicFreq(3):BosonicFreq(100) + @test_throws MethodError FermionicFreq(7):FermionicFreq(3):FermionicFreq(101) + end + + @testset "freqinvalid" begin + @test_throws ArgumentError BosonicFreq(2)==2 + @test_throws ArgumentError FermionicFreq(1)-1 + end + + @testset "unit tests" begin + @test_throws DomainError SparseIR.Statistics(2) + @test SparseIR.Statistics(0) + SparseIR.Statistics(1) == Fermionic() + @test Integer(FermionicFreq(19)) == 19 + @test -BosonicFreq(-24) == BosonicFreq(24) + @test sign(BosonicFreq(24)) == 1 + @test sign(BosonicFreq(0)) == 0 + @test sign(BosonicFreq(-94)) == -1 + @test BosonicFreq(24) % FermionicFreq(-7) == FermionicFreq(3) + @test FermionicFreq(123) % FermionicFreq(9) == BosonicFreq(6) + @test promote_type(BosonicFreq, FermionicFreq) == MatsubaraFreq + end + + @testset "findfirst (needs dimensionless div)" begin + @test BosonicFreq(100) ÷ BosonicFreq(50) === 2 + @test FermionicFreq(101) ÷ BosonicFreq(50) === 2 + haystack = FermionicFreq(-31):BosonicFreq(6):FermionicFreq(111) + needle = FermionicFreq(23) + @test findfirst(==(needle), haystack) === 10 # 6 * (10-1) + (-31) == 23 + @test haystack[findfirst(==(needle), haystack)] === needle + @test isnothing(findfirst(==(needle - oneunit(needle)), haystack)) + @test findfirst(==(first(haystack)), haystack) === 1 + @test findfirst(==(last(haystack)), haystack) === length(haystack) + end +end diff --git a/test/spir/integration_tests.jl b/test/spir/integration_tests.jl new file mode 100644 index 0000000..8318517 --- /dev/null +++ b/test/spir/integration_tests.jl @@ -0,0 +1,292 @@ +@testitem "Integration Test" tags=[:julia, :spir] begin + using SparseIR + using LinearAlgebra + using Random + + # Helper function to get dimensions array + function _get_dims(target_dim_size::Int, extra_dims::Vector{Int}, target_dim::Int) + ndim = length(extra_dims) + 1 + dims = zeros(Int, ndim) + dims[target_dim + 1] = target_dim_size # Julia is 1-indexed + pos = 1 + for i in 1:ndim + if i == target_dim + 1 + continue + end + dims[i] = extra_dims[pos] + pos += 1 + end + return dims + end + + # Helper function to compare tensors with relative error + function compare_tensors_with_relative_error( + a::Array{T,N}, b::Array{T,N}, tol) where { + T,N} + diff = abs.(a .- b) + ref = abs.(a) + max_diff = maximum(diff) + max_ref = maximum(ref) + + if max_diff > tol * max_ref + @info "max_diff: $max_diff" + @info "max_ref: $max_ref" + @info "tol: $tol" + return false + end + return true + end + + # Generate random coefficient based on type + function generate_random_coeff( + ::Type{Float64}, random_value_real, random_value_imag, pole) + return (2.0 * random_value_real - 1.0) * sqrt(abs(pole)) + end + + function generate_random_coeff( + ::Type{ComplexF64}, random_value_real, random_value_imag, pole) + return ComplexF64( + (2.0 * random_value_real - 1.0) * sqrt(abs(pole)), + (2.0 * random_value_imag - 1.0) * sqrt(abs(pole)) + ) + end + + # Main integration test function + function integration_test(::Type{T}, ::Type{S}, ::Type{K}, ndim::Int, + beta, wmax, epsilon, extra_dims, target_dim, tol, positive_only) where {T,S,K} + # positive_only is not supported for complex numbers + @assert !(T <: Complex && positive_only) + + @assert ndim == 1 + length(extra_dims) + + # IR basis + kernel = K(beta * wmax) + basis = FiniteTempBasis(S(), beta, wmax, epsilon; kernel) + basis_size = length(basis) + + # Tau Sampling + @info "Tau sampling" + tau_points = SparseIR.default_tau_sampling_points(basis) + num_tau_points = length(tau_points) + tau_sampling = TauSampling(basis; sampling_points=tau_points) + + @assert num_tau_points >= basis_size + @assert tau_sampling.sampling_points ≈ tau_points + + # Matsubara Sampling + @info "Matsubara sampling" + matsubara_points = SparseIR.default_matsubara_sampling_points( + basis; positive_only=positive_only) + num_matsubara_points = length(matsubara_points) + matsubara_sampling = MatsubaraSampling( + basis; positive_only=positive_only, sampling_points=matsubara_points) + if positive_only + @assert num_matsubara_points >= basis_size ÷ 2 + else + @assert num_matsubara_points >= basis_size + end + @assert Int.(matsubara_sampling.sampling_points) == matsubara_points + + # DLR + @info "DLR" + dlr = DiscreteLehmannRepresentation(basis) + npoles = SparseIR.npoles(dlr) + poles = SparseIR.get_poles(dlr) + @assert npoles >= basis_size + @assert length(poles) == npoles + + # Calculate total size of extra dimensions + extra_size = prod(extra_dims) + + # Generate random DLR coefficients + Random.seed!(982743) + coeffs_targetdim0_dims = _get_dims(npoles, extra_dims, 0) + coeffs_targetdim0 = Array{T}(undef, coeffs_targetdim0_dims...) + + # Fill with random values + coeffs_2d = reshape(coeffs_targetdim0, npoles, extra_size) + for i in 1:npoles + for j in 1:extra_size + coeffs_2d[i, j] = generate_random_coeff(T, rand(), rand(), poles[i]) + end + end + + # DLR sampling objects + tau_sampling_dlr = TauSampling(dlr; sampling_points=tau_points) + matsubara_sampling_dlr = MatsubaraSampling( + dlr; positive_only=positive_only, sampling_points=matsubara_points) + + # Move the axis for the poles from the first to the target dimension + perm = collect(1:ndim) + perm[1], perm[target_dim + 1] = perm[target_dim + 1], perm[1] + coeffs = permutedims(coeffs_targetdim0, perm) + + # Convert DLR coefficients to IR coefficients + g_IR = SparseIR.to_IR(dlr, coeffs, target_dim + 1) # Julia is 1-indexed + + # Convert IR coefficients back to DLR coefficients + g_DLR_reconst = SparseIR.from_IR(dlr, g_IR, target_dim + 1) + + # Compare the Greens function at all tau points between IR and DLR + # Instead of using basis functions directly, we'll use the sampling objects + # to evaluate at tau points + + # Evaluate g_IR at tau points using tau_sampling + gtau_from_IR_dims = collect(size(g_IR)) + gtau_from_IR_dims[target_dim + 1] = num_tau_points + gtau_from_IR = similar(g_IR, T, gtau_from_IR_dims...) + evaluate!(gtau_from_IR, tau_sampling, g_IR; dim=target_dim + 1) + + # Evaluate DLR coefficients at tau points using tau_sampling_dlr + gtau_from_DLR_dims = collect(size(coeffs)) + gtau_from_DLR_dims[target_dim + 1] = num_tau_points + gtau_from_DLR = similar(coeffs, T, gtau_from_DLR_dims...) + evaluate!(gtau_from_DLR, tau_sampling_dlr, coeffs; dim=target_dim + 1) + + # Evaluate reconstructed DLR at tau points + gtau_from_DLR_reconst_dims = collect(size(g_DLR_reconst)) + gtau_from_DLR_reconst_dims[target_dim + 1] = num_tau_points + gtau_from_DLR_reconst = similar(g_DLR_reconst, T, gtau_from_DLR_reconst_dims...) + evaluate!( + gtau_from_DLR_reconst, tau_sampling_dlr, g_DLR_reconst; dim=target_dim + + 1) + + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR, tol) + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR_reconst, tol) + + # Use sampling to evaluate the Greens function at all tau points between IR and DLR + gtau_from_DLR_sampling = similar(gtau_from_DLR) + evaluate!(gtau_from_DLR_sampling, tau_sampling_dlr, coeffs; dim=target_dim + 1) + @test compare_tensors_with_relative_error(gtau_from_IR, gtau_from_DLR_sampling, tol) + + # Compare the Greens function at all Matsubara frequencies between IR and DLR + # Use sampling objects to evaluate at Matsubara frequencies + + # Evaluate g_IR at Matsubara frequencies using matsubara_sampling + giw_from_IR_dims = collect(size(g_IR)) + giw_from_IR_dims[target_dim + 1] = num_matsubara_points + giw_from_IR = similar(g_IR, ComplexF64, giw_from_IR_dims...) + evaluate!(giw_from_IR, matsubara_sampling, g_IR; dim=target_dim + 1) + + # Evaluate DLR coefficients at Matsubara frequencies using matsubara_sampling_dlr + giw_from_DLR_dims = collect(size(coeffs)) + giw_from_DLR_dims[target_dim + 1] = num_matsubara_points + giw_from_DLR = similar(coeffs, ComplexF64, giw_from_DLR_dims...) + evaluate!(giw_from_DLR, matsubara_sampling_dlr, coeffs; dim=target_dim + 1) + + @test compare_tensors_with_relative_error(giw_from_IR, giw_from_DLR, tol) + + # Use sampling to evaluate the Greens function at all Matsubara frequencies + giw_from_DLR_sampling = similar(giw_from_DLR, ComplexF64) + evaluate!(giw_from_DLR_sampling, matsubara_sampling_dlr, coeffs; dim=target_dim + 1) + @test compare_tensors_with_relative_error(giw_from_IR, giw_from_DLR_sampling, tol) + + # Prepare arrays for transformations + # Use the actual dimensions from g_IR to ensure consistency + gIR_dims = collect(size(g_IR)) + gIR = Array{T}(undef, gIR_dims...) + gIR2 = Array{T}(undef, gIR_dims...) + + # For gtau, use tau_points along target dimension + gtau_dims = collect(size(g_IR)) + gtau_dims[target_dim + 1] = num_tau_points + gtau = Array{T}(undef, gtau_dims...) + + # For giw_reconst, use matsubara_points along target dimension + giw_reconst_dims = collect(size(g_IR)) + giw_reconst_dims[target_dim + 1] = num_matsubara_points + giw_reconst = Array{ComplexF64}(undef, giw_reconst_dims...) + + # Matsubara -> IR + if T <: Real + gIR_work = Array{ComplexF64}(undef, gIR_dims...) + fit!(gIR_work, matsubara_sampling, giw_from_DLR; dim=target_dim + 1) + gIR .= real.(gIR_work) + else + fit!(gIR, matsubara_sampling, giw_from_DLR; dim=target_dim + 1) + end + + # IR -> tau + evaluate!(gtau, tau_sampling, gIR; dim=target_dim + 1) + + # tau -> IR + fit!(gIR2, tau_sampling, gtau; dim=target_dim + 1) + + # IR -> Matsubara + evaluate!(giw_reconst, matsubara_sampling, gIR2; dim=target_dim + 1) + + giw_from_IR_reconst = similar(giw_reconst) + evaluate!(giw_from_IR_reconst, matsubara_sampling, gIR2; dim=target_dim + 1) + @test compare_tensors_with_relative_error(giw_from_DLR, giw_from_IR_reconst, tol) + + # Note: Julia uses automatic garbage collection with finalizers for C resource cleanup. + # Unlike the C_API version, we don't need explicit release calls. + end + + # Test _get_dims helper function + @testset "_get_dims" begin + extra_dims = [2, 3, 4] + + # Test target_dim = 0 + dims = _get_dims(100, extra_dims, 0) + @test dims == [100, 2, 3, 4] + + # Test target_dim = 1 + dims = _get_dims(100, extra_dims, 1) + @test dims == [2, 100, 3, 4] + + # Test target_dim = 2 + dims = _get_dims(100, extra_dims, 2) + @test dims == [2, 3, 100, 4] + + # Test target_dim = 3 + dims = _get_dims(100, extra_dims, 3) + @test dims == [2, 3, 4, 100] + end + + # Run integration tests + beta = 1e+4 + wmax = 2.0 + epsilon = 1e-10 + tol = 10 * epsilon + + @testset "Integration Tests" begin + for positive_only in [false, true] + @info "positive_only = $positive_only" + + # 1D tests + extra_dims = Int[] + @info "Integration test for bosonic LogisticKernel" + integration_test(Float64, SparseIR.Bosonic, SparseIR.LogisticKernel, 1, + beta, wmax, epsilon, extra_dims, 0, tol, positive_only) + + @info "Integration test for fermionic LogisticKernel" + integration_test(Float64, SparseIR.Fermionic, SparseIR.LogisticKernel, 1, + beta, wmax, epsilon, extra_dims, 0, tol, positive_only) + + if !positive_only + integration_test(ComplexF64, SparseIR.Bosonic, SparseIR.LogisticKernel, 1, + beta, wmax, epsilon, extra_dims, 0, tol, positive_only) + + integration_test( + ComplexF64, SparseIR.Fermionic, SparseIR.LogisticKernel, 1, + beta, wmax, epsilon, extra_dims, 0, tol, positive_only) + end + + # 4D tests with extra_dims = [2, 3, 4] + for target_dim in 0:3 + extra_dims = [2, 3, 4] + @info "Integration test for bosonic LogisticKernel, target_dim = $target_dim" + integration_test(Float64, SparseIR.Bosonic, SparseIR.LogisticKernel, 4, + beta, wmax, epsilon, extra_dims, target_dim, tol, positive_only) + + # Also test complex for multi-dimensional arrays when positive_only=false + if !positive_only && target_dim == 0 + integration_test( + ComplexF64, SparseIR.Bosonic, SparseIR.LogisticKernel, 4, + beta, wmax, epsilon, extra_dims, target_dim, tol, positive_only) + end + end + end + end +end diff --git a/test/spir/kernel_tests.jl b/test/spir/kernel_tests.jl new file mode 100644 index 0000000..7a7c904 --- /dev/null +++ b/test/spir/kernel_tests.jl @@ -0,0 +1,15 @@ +@testitem "kernel.jl" tags=[:julia, :spir] begin + import SparseIR as SparseIR + + @testset "Logistic kernel" begin + lam = 42 + kernel = LogisticKernel(lam) + @test SparseIR.Λ(kernel) == lam + end + + @testset "Regularized Bose kernel" begin + lam = 42 + kernel = RegularizedBoseKernel(lam) + @test SparseIR.Λ(kernel) == lam + end +end diff --git a/test/spir/poly_tests.jl b/test/spir/poly_tests.jl new file mode 100644 index 0000000..9a24c69 --- /dev/null +++ b/test/spir/poly_tests.jl @@ -0,0 +1,44 @@ +@testitem "poly.jl" tags=[:julia, :lib] begin + using Test + using SparseIR + import SparseIR as SparseIR + + β = 10 + ωmax = 4 + ε = 1e-6 + basis = FiniteTempBasis{Fermionic}(β, ωmax, ε) + + ρ₀(ω) = 2 / π * √(1 - clamp(ω, -1, +1)^2) + + @testset "u" begin + @test SparseIR.xmin(basis.u) == 0.0 + @test SparseIR.xmax(basis.u) == β + end + + @testset "v" begin + @test SparseIR.xmin(basis.v) == -ωmax + @test SparseIR.xmax(basis.v) == ωmax + end + + @testset "uhat" begin + @test SparseIR.xmin(basis.uhat) == -1.0 + @test SparseIR.xmax(basis.uhat) == 1.0 + end + + @testset "overlap" begin + ref_u = [0.27517437799713756, -0.33320547877174056, 0.20676709933869278, + -0.07612175747193344, -0.04128171229889062, + 0.09679420081388303, -0.0989389721622601, 0.06747728769454617, + -0.023901271045533704, -0.014714625415717688, + 0.03830797967004657, -0.043884983551122116, 0.034203081565519565, + -0.015618570179708689, -0.004366240169617414, 0.01933538369730581] + @test overlap(basis.u, ρ₀) ≈ ref_u + ref_v = [0.6352548229644916, -2.4069288229178198e-17, -0.22782525665797093, + -1.0842021724855044e-17, -0.1783957405542744, + -8.023096076392733e-18, 0.18907994546506804, 8.348356728138384e-18, + -0.07277201662947068, 2.3852447794681098e-18, + -0.019708084751718376, -1.919037845299343e-17, 0.05824932416884472, + -4.9873299934333204e-18, -0.05617142181206885, 1.734723475976807e-18] + @test overlap(basis.v, ρ₀) ≈ ref_v + end +end diff --git a/test/spir/sampling_tests.jl b/test/spir/sampling_tests.jl new file mode 100644 index 0000000..602cee8 --- /dev/null +++ b/test/spir/sampling_tests.jl @@ -0,0 +1,157 @@ +@testitem "sampling" tags=[:julia, :spir] begin + using Test + using Random + using SparseIR + using LinearAlgebra: norm + import SparseIR as SparseIR + + function getperm(N, src, dst) + perm = collect(1:N) + deleteat!(perm, src) + insert!(perm, dst, src) + return perm + end + + """ + movedim(arr::AbstractArray, src => dst) + + Move `arr`'s dimension at `src` to `dst` while keeping the order of the remaining + dimensions unchanged. + """ + function movedim(arr::AbstractArray{T,N}, src, dst) where {T,N} + src == dst && return arr + return permutedims(arr, getperm(N, src, dst)) + end + + @testset "fit from tau with stat = $stat, Λ = $Λ" for stat in (Bosonic(), Fermionic()), + Λ in (10, 42) + + sve_logistic = SparseIR.SVEResult(LogisticKernel(Λ), 1e-10) + basis = FiniteTempBasis(stat, 1, Λ, 1e-10; sve_result=sve_logistic) + smpl = TauSampling(basis) + @test issorted(smpl.sampling_points) + Random.seed!(5318008) + + shape = (2, 3, 4) + rhol = randn(ComplexF64, (length(basis), shape...)) + originalgl = -basis.s .* rhol + for dim in 1:ndims(rhol) + gl = movedim(originalgl, 1, dim) + gtau = evaluate(smpl, gl; dim) + @test size(gtau) == (size(gl)[1:(dim - 1)]..., + length(smpl.sampling_points), + size(gl)[(dim + 1):end]...) + + gl_from_tau = fit(smpl, gtau; dim) + @test gl_from_tau ≈ gl + + gl_from_tau2 = similar(gl_from_tau) + fit!(gl_from_tau2, smpl, gtau; dim) + @test gl_from_tau2 ≈ gl + end + end + + @testset "τ noise with stat = $stat, Λ = $Λ" for stat in (Bosonic(), Fermionic()), + Λ in (10, 42) + + sve_logistic = SparseIR.SVEResult(LogisticKernel(Λ), 1e-10) + basis = FiniteTempBasis(stat, 1, Λ, 1e-10; sve_result=sve_logistic) + smpl = TauSampling(basis) + @test basis === SparseIR.basis(smpl) + @test issorted(smpl.sampling_points) + Random.seed!(5318008) + + ρℓ = basis.v([-0.999, -0.01, 0.5]) * [0.8, -0.2, 0.5] + Gℓ = basis.s .* ρℓ + Gℓ_magn = norm(Gℓ) + @inferred evaluate(smpl, Gℓ) + @inferred evaluate(smpl, Gℓ, dim=1) + Gτ = evaluate(smpl, Gℓ) + + Gτ_inplace = similar(Gτ) + evaluate!(Gτ_inplace, smpl, Gℓ) + @test Gτ == Gτ_inplace + + noise = 1e-5 + Gτ_n = Gτ + noise * norm(Gτ) * randn(size(Gτ)...) + @inferred fit(smpl, Gτ_n) + @inferred fit(smpl, Gτ_n, dim=1) + Gℓ_n = fit(smpl, Gτ_n) + + Gℓ_n_inplace = similar(Gℓ_n) + fit!(Gℓ_n_inplace, smpl, Gτ_n) + @test Gℓ_n == Gℓ_n_inplace + + @test isapprox(Gℓ, Gℓ_n, atol=12 * noise * Gℓ_magn, rtol=0) + end + + @testset "iω noise with stat = $stat, Λ = $Λ" for stat in (Bosonic(), Fermionic()), + Λ in (10, 42), + positive_only in (false, true) + + sve_logistic = SparseIR.SVEResult(LogisticKernel(Λ), 1e-10) + basis = FiniteTempBasis(stat, 1, Λ, 1e-10; sve_result=sve_logistic) + smpl = MatsubaraSampling(basis; positive_only) + @test basis === SparseIR.basis(smpl) + #=if !positive_only + @test smpl isa + (stat == Fermionic() ? MatsubaraSampling64F : MatsubaraSampling64B) + end + =# + @test issorted(smpl.sampling_points) + Random.seed!(1312 + 161) + + ρℓ = basis.v([-0.999, -0.01, 0.5]) * [0.8, -0.2, 0.5] + Gℓ = basis.s .* ρℓ + Gℓ_magn = norm(Gℓ) + @inferred evaluate(smpl, Gℓ) + @inferred evaluate(smpl, Gℓ, dim=1) + Giw = evaluate(smpl, Gℓ) + + Giw_inplace = similar(Giw) + evaluate!(Giw_inplace, smpl, Gℓ) + @test Giw == Giw_inplace + + noise = 1e-5 + Giwn_n = Giw + noise * norm(Giw) * randn(size(Giw)...) + @inferred fit(smpl, Giwn_n) + @inferred fit(smpl, Giwn_n, dim=1) + Gℓ_n = fit(smpl, Giwn_n) + @test isapprox(Gℓ, Gℓ_n, atol=40 * sqrt(1 + positive_only) * noise * Gℓ_magn, + rtol=0) + + Gℓ_n_inplace = similar(Gℓ_n) + fit!(Gℓ_n_inplace, smpl, Giwn_n) + @test Gℓ_n == Gℓ_n_inplace + end + + @testset "errors with stat = $stat, $sampling" for stat in (Bosonic(), Fermionic()), + sampling in (TauSampling, + MatsubaraSampling) + + basis = FiniteTempBasis(stat, 3, 3, 1e-6) + smpl = sampling(basis) + @test_throws DimensionMismatch evaluate(smpl, rand(100)) + @test_throws DimensionMismatch evaluate!(rand(100), smpl, rand(100)) + @test_throws Exception fit(smpl, rand(100)) + @test_throws DimensionMismatch fit!(rand(100), smpl, rand(100)) + end + + @testset "frequency range" begin + basis = FiniteTempBasis{Fermionic}(3, 3, 1e-6) + freqrange = SparseIR.frequency_range(12) + smpl = MatsubaraSampling(basis; sampling_points=freqrange) + @test sampling_points(smpl) !== freqrange + @test sampling_points(smpl) == freqrange + end + + @testset "default_matsubara_sampling_points" begin + β = 10.0 + ωmax = 1.0 + ε = 1e-10 + kernel = LogisticKernel(β * ωmax) + basis = FiniteTempBasis(Fermionic(), β, ωmax, ε; kernel) + points = SparseIR.default_matsubara_sampling_points(basis) + @test length(points) > 0 + end +end diff --git a/test/spir/sve_tests.jl b/test/spir/sve_tests.jl new file mode 100644 index 0000000..bf32ddb --- /dev/null +++ b/test/spir/sve_tests.jl @@ -0,0 +1,15 @@ +@testitem "sve.jl" tags=[:julia, :lib] begin + using SparseIR + + @testset "sve_result/LogisticKernel" begin + kernel = LogisticKernel(10) + sve_result = SparseIR.SVEResult(kernel, 1e-10) + @test true + end + + @testset "sve_result/RegularizedBoseKernel" begin + kernel = RegularizedBoseKernel(10) + sve_result = SparseIR.SVEResult(kernel, 1e-10) + @test true + end +end diff --git a/test/svd.jl b/test/svd.jl deleted file mode 100644 index 93900db..0000000 --- a/test/svd.jl +++ /dev/null @@ -1,22 +0,0 @@ -@testitem "svd.jl" begin - using LinearAlgebra - - mat64x2 = SparseIR.Float64x2.(rand(4, 6)) - @test_logs (:info, - "n_sv_hint is set but will not be used in the current implementation!") SparseIR.compute_svd( - mat64x2; - n_sv_hint=2) - @test_logs (:info, - "strategy is set but will not be used in the current implementation!") SparseIR.compute_svd( - mat64x2; - strategy=:accurate) - - mat = rand(5, 6) - @test_logs (:info, - "n_sv_hint is set but will not be used in the current implementation!") SparseIR.compute_svd( - mat; - n_sv_hint=2) - u, s, v = SparseIR.compute_svd(mat; strategy=:accurate) - @test u * Diagonal(s) * v' ≈ mat - @test_throws DomainError SparseIR.compute_svd(mat; strategy=:fast) -end diff --git a/test/sve.jl b/test/sve.jl deleted file mode 100644 index 70e8591..0000000 --- a/test/sve.jl +++ /dev/null @@ -1,110 +0,0 @@ -@testsnippet SVETestsFunctions begin - function check_smooth(u, s, uscale, fudge_factor) - ε = eps(eltype(s)) - x = SparseIR.knots(u)[(begin + 1):(end - 1)] - - jump = abs.(u(x .+ ε) - u(x .- ε)) - compare_below = abs.(u(x .- ε) - u(x .- 3ε)) - compare_above = abs.(u(x .+ 3ε) - u(x .+ ε)) - compare = min.(compare_below, compare_above) - compare = max.(compare, uscale * ε) - - # loss of precision - compare .*= fudge_factor .* (first(s) ./ s) - @test all(jump .< compare) - end - - leaq(a, b; kwargs...) = (a <= b) || isapprox(a, b; kwargs...) - a ⪅ b = leaq(a, b) -end - -@testitem "smooth" setup=[SVETestsFunctions, CommonTestData] begin - for Λ in (10, 42, 10_000) - basis = FiniteTempBasis{Fermionic}(1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - check_smooth(basis.u, basis.s, 2 * maximum(basis.u(1)), 24) - check_smooth(basis.v, basis.s, 50, 200) - end -end - -@testitem "num roots u" setup=[CommonTestData] begin - for Λ in (10, 42, 10_000) - basis = FiniteTempBasis{Fermionic}(1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - for ui in basis.u - ui_roots = SparseIR.roots(ui) - @test length(ui_roots) == ui.l - end - end -end - -@testitem "num roots û" setup=[CommonTestData] begin - for stat in (Fermionic(), Bosonic()), - Λ in (10, 42, 10_000) - - basis = FiniteTempBasis(stat, 1, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - for i in [1, 2, 8, 11] - x₀ = SparseIR.find_extrema(basis.uhat[i]) - @test i ≤ length(x₀) ≤ i + 1 - end - end -end - -@testitem "accuracy" setup=[SVETestsFunctions, CommonTestData] begin - for stat in (Fermionic(), Bosonic()), - Λ in (10, 42, 10_000) - - basis = FiniteTempBasis(stat, 4, Λ; sve_result=CommonTestData.sve_logistic[Λ]) - @test 0 < SparseIR.accuracy(basis) ⪅ last(SparseIR.significance(basis)) - @test isone(first(SparseIR.significance(basis))) - @test SparseIR.accuracy(basis) ⪅ last(basis.s) / first(basis.s) - end -end - -@testitem "choose_accuracy" begin - using Logging: with_logger, NullLogger - - with_logger(NullLogger()) do # suppress output of warnings - @test SparseIR.choose_accuracy(nothing, nothing) == - (2.2204460492503131e-16, SparseIR.Float64x2, :default) - @test SparseIR.choose_accuracy(nothing, Float64) == - (1.4901161193847656e-8, Float64, :default) - @test SparseIR.choose_accuracy(nothing, SparseIR.Float64x2) == - (2.2204460492503131e-16, SparseIR.Float64x2, :default) - @test SparseIR.choose_accuracy(1e-6, nothing) == (1.0e-6, Float64, :default) - @test SparseIR.choose_accuracy(1e-8, nothing) == - (1.0e-8, SparseIR.Float64x2, :default) - - @test SparseIR.choose_accuracy(1e-20, nothing) == - (1.0e-20, SparseIR.Float64x2, :default) - @test_logs (:warn, - """Basis cutoff is 1.0e-20, which is below √ε with ε = 4.9303806576313238e-32. -Expect singular values and basis functions for large l to have lower precision -than the cutoff.""") SparseIR.choose_accuracy(1e-20, nothing) - - @test SparseIR.choose_accuracy(1e-10, Float64) == (1.0e-10, Float64, :accurate) - @test_logs (:warn, - """Basis cutoff is 1.0e-10, which is below √ε with ε = 2.220446049250313e-16. -Expect singular values and basis functions for large l to have lower precision -than the cutoff.""") SparseIR.choose_accuracy(1e-10, Float64) - - @test SparseIR.choose_accuracy(1e-6, Float64) == (1.0e-6, Float64, :default) - - @test SparseIR.choose_accuracy(1e-6, Float64, :auto) == - (1.0e-6, Float64, :default) - @test SparseIR.choose_accuracy(1e-6, Float64, :accurate) == - (1.0e-6, Float64, :accurate) - end -end - -@testitem "truncate" begin - sve = SparseIR.CentrosymmSVE(LogisticKernel(5), 1e-6, Float64) - - svds = SparseIR.compute_svd.(SparseIR.matrices(sve)) - u_, s_, v_ = zip(svds...) - - for lmax in 3:20 - u, s, v = SparseIR.truncate(u_, s_, v_; lmax) - u, s, v = SparseIR.postprocess(sve, u, s, v) - @test length(u) == length(s) == length(v) - @test length(s) ≤ lmax - 1 - end -end From c5df6e665f69a36401e0e32ecaa19417a8caffc9 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sun, 14 Sep 2025 11:38:13 +0900 Subject: [PATCH 02/14] Support libsparseir_jll (#82) --- Project.toml | 6 ++ README.md | 4 ++ deps/Project.toml | 3 - deps/build.jl | 33 ---------- deps/generator.toml | 8 --- deps/prologue.jl | 3 - development.md | 145 ++++++++++++++++++++++++++++++++++++++++++++ src/C_API.jl | 6 +- 8 files changed, 157 insertions(+), 51 deletions(-) delete mode 100644 deps/Project.toml delete mode 100644 deps/build.jl delete mode 100644 deps/generator.toml delete mode 100644 deps/prologue.jl create mode 100644 development.md diff --git a/Project.toml b/Project.toml index aba6bda..d2793bb 100644 --- a/Project.toml +++ b/Project.toml @@ -8,17 +8,22 @@ CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +libsparseir_jll = "e5bf0e3f-5e75-5bb8-9f3f-2c07620468a3" [compat] Aqua = "0.8.14" CEnum = "0.5.0" +Clang = "0.19.1" Libdl = "1" LinearAlgebra = "1" QuadGK = "2.11.2" StableRNGs = "1.0.3" +julia = "1.6" +libsparseir_jll = "0.4.2" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +Clang = "40e3b903-d033-50b4-a0cc-940c62c95e31" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" @@ -26,3 +31,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Aqua", "Test", "ReTestItems", "Random", "StableRNGs"] +dev = ["Clang"] diff --git a/README.md b/README.md index 468c17c..b62baf7 100644 --- a/README.md +++ b/README.md @@ -166,3 +166,7 @@ MINIMAX isometry method (Merzuk Kaltak and Georg Kresse, [aqua-url]: https://github.com/JuliaTesting/Aqua.jl [issues-url]: https://github.com/SpM-lab/SparseIR.jl/issues + +Development +----------- +See [development.md](development.md) for more information on how to contribute to the project. \ No newline at end of file diff --git a/deps/Project.toml b/deps/Project.toml deleted file mode 100644 index b72c81c..0000000 --- a/deps/Project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[deps] -Clang = "40e3b903-d033-50b4-a0cc-940c62c95e31" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index ea347d7..0000000 --- a/deps/build.jl +++ /dev/null @@ -1,33 +0,0 @@ -using Pkg -Pkg.activate(@__DIR__) -Pkg.instantiate() - -using Clang.Generators -using Clang.LibClang.Clang_jll - -include_dir = normpath(joinpath(@__DIR__, "../../libsparseir/include")) -sparseir_dir = joinpath(include_dir, "sparseir") - -# wrapper generator options -options = load_options(joinpath(@__DIR__, "generator.toml")) - -# add compiler flags, e.g. "-DXXXXXXXXX" -args = get_default_args() -push!(args, "-I$include_dir") - -headers = [joinpath(sparseir_dir, header) - for header in readdir(sparseir_dir) if endswith(header, ".h")] -# there is also an experimental `detect_headers` function for auto-detecting top-level headers in the directory -# headers = detect_headers(sparseir_dir, args) - -# create context -ctx = create_context(headers, args, options) - -# run generator -build!(ctx) - -# Replace line 28 with: -file_path = joinpath(@__DIR__, "../src/C_API.jl") -content = read(file_path, String) -content = replace(content, "const c_complex = ComplexF32" => "const c_complex = ComplexF64") -write(file_path, content) diff --git a/deps/generator.toml b/deps/generator.toml deleted file mode 100644 index e209955..0000000 --- a/deps/generator.toml +++ /dev/null @@ -1,8 +0,0 @@ -[general] -prologue_file_path = "./prologue.jl" -library_name = "libsparseir" -output_file_path = "./../src/C_API.jl" -module_name = "C_API" -# jll_pkg_name = "LibSparseIR_jll" -export_symbol_prefixes = ["spir_", "SPIR_"] -extract_c_comment_style = "doxygen" diff --git a/deps/prologue.jl b/deps/prologue.jl deleted file mode 100644 index 5cfe134..0000000 --- a/deps/prologue.jl +++ /dev/null @@ -1,3 +0,0 @@ -using Libdl: dlext - -libsparseir = expanduser("~/opt/libsparseir/lib/libsparseir.$(dlext)") diff --git a/development.md b/development.md new file mode 100644 index 0000000..92af899 --- /dev/null +++ b/development.md @@ -0,0 +1,145 @@ +# Development Guide + +This document describes how to set up and use the development environment for SparseIR.jl. + +## Prerequisites + +- Julia 1.6 or later +- Git + +## Setting up the Development Environment + +### 1. Clone the Repository + +```bash +git clone https://github.com/SpM-lab/SparseIR.jl.git +cd SparseIR.jl +``` + +### 2. Activate the Project Environment + +```bash +# Activate the project environment +julia --project=. +``` + +Or from within Julia: + +```julia +using Pkg +Pkg.activate(".") +``` + +### 3. Install Dependencies + +#### Install Main Dependencies + +```julia +# Install main dependencies (automatically done with --project=.) +Pkg.instantiate() +``` + +#### Install Development Dependencies + +```julia +# Install development dependencies (includes Clang for code generation) +Pkg.instantiate(; target="dev") +``` + +#### Install Test Dependencies + +```julia +# Install test dependencies +Pkg.instantiate(; target="test") +``` + +### 4. Verify Installation + +```julia +# Check installed packages +Pkg.status() + +# Check development dependencies +Pkg.status(; target="dev") + +# Check test dependencies +Pkg.status(; target="test") +``` + +## Development Workflow + +### Running Tests + +```bash +# Run all tests +julia --project=. -e "using Pkg; Pkg.test()" + +# Or run tests with specific target +julia --project=. --target=test -e "using Pkg; Pkg.test()" +``` + +### Code Generation + +The project uses Clang.jl for generating C API bindings. To regenerate the C API: + +```julia +# Activate development environment +julia --project=. --target=dev + +# Run the build script +include("deps/build.jl") +``` + +### Building Documentation + +```bash +# Navigate to docs directory +cd docs + +# Activate docs environment and build +julia --project=. -e "using Pkg; Pkg.instantiate()" +julia --project=. docs/make.jl +``` + +## Project Structure + +- `src/` - Main source code +- `test/` - Test files +- `deps/` - Build dependencies and code generation scripts +- `docs/` - Documentation source +- `assets/` - Static assets for documentation + +## Troubleshooting + +1. **Package not found**: Make sure you've activated the correct target: + ```julia + Pkg.instantiate(; target="dev") # for development dependencies + Pkg.instantiate(; target="test") # for test dependencies + ``` + +2. **Clang.jl issues**: Ensure you have the development target activated: + ```bash + julia --project=. --target=dev + ``` + +3. **Build failures**: Check that all dependencies are properly installed: + ```julia + Pkg.status() + Pkg.status(; target="dev") + ``` + +### Getting Help + +- Check the [main documentation](README.md) +- Open an issue on GitHub +- Check Julia's package manager documentation + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Run tests to ensure everything works +5. Submit a pull request + +For more detailed contribution guidelines, see the main repository documentation. diff --git a/src/C_API.jl b/src/C_API.jl index b2ad906..df4a605 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -1,11 +1,9 @@ module C_API using CEnum: CEnum, @cenum +using libsparseir_jll -using Libdl: dlext - -libsparseir = expanduser("~/opt/libsparseir/lib/libsparseir.$(dlext)") - +const libsparseir = libsparseir_jll.libsparseir const c_complex = ComplexF64 mutable struct _spir_kernel end From 7fbc29a50947ba2ec7775d23fc8dac0b6b4bee0a Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sun, 14 Sep 2025 16:09:35 +0900 Subject: [PATCH 03/14] Add debug mode --- src/C_API.jl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/C_API.jl b/src/C_API.jl index df4a605..359ec06 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -3,7 +3,23 @@ module C_API using CEnum: CEnum, @cenum using libsparseir_jll -const libsparseir = libsparseir_jll.libsparseir +function get_libsparseir() + # Use debug library if SPARSEIR_LIB_PATH environment variable is set + if haskey(ENV, "SPARSEIR_LIB_PATH") + debug_path = ENV["SPARSEIR_LIB_PATH"] + if isfile(debug_path) + @info "Using debug library: $debug_path" + return debug_path + else + @warn "Debug library not found at $debug_path, falling back to JLL" + end + end + + # Production: use JLL package + return libsparseir_jll.libsparseir +end + +const libsparseir = get_libsparseir() const c_complex = ComplexF64 mutable struct _spir_kernel end From 1ada9327a5fe0a9b2e30df9dfbd1cf469ffe53d5 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sun, 14 Sep 2025 17:37:55 +0900 Subject: [PATCH 04/14] Fix stack overflow and other build fails --- .github/workflows/CI.yml | 30 +++++++++++++++++++----------- docs/Project.toml | 2 +- docs/make.jl | 5 +++-- docs/src/private.md | 5 ----- src/dlr.jl | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b39ddd9..9e42fcd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -25,16 +25,25 @@ jobs: strategy: fail-fast: false matrix: - version: - - 'lts' - - '1' - - 'nightly' - os: - - ubuntu-latest - - macOS-latest - - windows-latest - arch: - - x64 + include: + - version: 'lts' + os: ubuntu-latest + arch: x64 + - version: '1' + os: ubuntu-latest + arch: x64 + - version: 'lts' + os: macOS-latest + arch: arm64 + - version: '1' + os: macOS-latest + arch: arm64 + - version: 'lts' + os: windows-latest + arch: x64 + - version: '1' + os: windows-latest + arch: x64 steps: - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 @@ -44,7 +53,6 @@ jobs: - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - continue-on-error: ${{ matrix.version == 'nightly' }} - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v5 docs: diff --git a/docs/Project.toml b/docs/Project.toml index 5feee93..664d90b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,5 +5,5 @@ SparseIR = "4fe2279e-80f0-4adb-8463-ee114ff56b7d" [compat] Documenter = "1" -SparseIR = "1" +SparseIR = "2" julia = "1.6" diff --git a/docs/make.jl b/docs/make.jl index adf7912..53acf33 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,8 +10,9 @@ bib = CitationBibliography( ) makedocs(; modules=[SparseIR], - authors="Samuel Badr and contributors", - # repo="https://github.com/SpM-lab/SparseIR.jl/blob/{commit}{path}#{line}", + checkdocs=:none, + authors="SatoshiTerasaki , Samuel Badr , Hiroshi Shinaoka , Markus Wallerberger ", + repo="https://github.com/SpM-lab/SparseIR.jl/blob/{commit}{path}#{line}", sitename="SparseIR.jl", format=Documenter.HTML(; prettyurls=get(ENV, "CI", "false") == "true", diff --git a/docs/src/private.md b/docs/src/private.md index 33bb490..d869f80 100644 --- a/docs/src/private.md +++ b/docs/src/private.md @@ -12,8 +12,3 @@ Private = true Public = false ``` -```@autodocs -Modules = [SparseIR._LinAlg] -Private = true -Public = true -``` diff --git a/src/dlr.jl b/src/dlr.jl index 6c1baf8..ea65145 100644 --- a/src/dlr.jl +++ b/src/dlr.jl @@ -37,7 +37,7 @@ Construct a DLR basis from an IR basis. If `poles` is not provided, uses the default omega sampling points from the IR basis. """ function DiscreteLehmannRepresentation( - basis::AbstractBasis, poles=default_omega_sampling_points(basis)) + basis::AbstractBasis, poles::Vector{Float64}=default_omega_sampling_points(basis)) status = Ref{Int32}(-100) dlr_ptr = C_API.spir_dlr_new_with_poles(basis.ptr, length(poles), poles, status) status[] == C_API.SPIR_COMPUTATION_SUCCESS || From 0fb549776d578c0662baf802d81abf0db2c46b69 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Sun, 14 Sep 2025 17:57:14 +0900 Subject: [PATCH 05/14] Disable windows test --- .github/workflows/CI.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9e42fcd..765555a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -38,12 +38,13 @@ jobs: - version: '1' os: macOS-latest arch: arm64 - - version: 'lts' - os: windows-latest - arch: x64 - - version: '1' - os: windows-latest - arch: x64 + # libsparseir is not yet tested on Windows + #- version: 'lts' + #os: windows-latest + #arch: x64 + #- version: '1' + #os: windows-latest + #arch: x64 steps: - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v2 From 0c9aa33779abdb60307b87de28d8794709e4b042 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Mon, 15 Sep 2025 08:31:27 +0900 Subject: [PATCH 06/14] Add more sanity checks --- .github/workflows/CI.yml | 7 +++--- src/dlr.jl | 35 ++++++++++++++++++++++++++ src/sampling.jl | 49 ++++++++++++++++++++++++++++--------- test/spir/sampling_tests.jl | 2 ++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 765555a..d6912af 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,6 +6,7 @@ on: pull_request: branches: - main + - develop_v2xx workflow_dispatch: schedule: - cron: 0 0 * * 1 @@ -39,9 +40,9 @@ jobs: os: macOS-latest arch: arm64 # libsparseir is not yet tested on Windows - #- version: 'lts' - #os: windows-latest - #arch: x64 + - version: 'lts' + os: windows-latest + arch: x64 #- version: '1' #os: windows-latest #arch: x64 diff --git a/src/dlr.jl b/src/dlr.jl index ea65145..d229885 100644 --- a/src/dlr.jl +++ b/src/dlr.jl @@ -38,6 +38,14 @@ If `poles` is not provided, uses the default omega sampling points from the IR b """ function DiscreteLehmannRepresentation( basis::AbstractBasis, poles::Vector{Float64}=default_omega_sampling_points(basis)) + # Safety checks + if !_is_column_major_contiguous(poles) + error("Poles array must be contiguous") + end + if length(poles) == 0 + error("Poles array cannot be empty") + end + status = Ref{Int32}(-100) dlr_ptr = C_API.spir_dlr_new_with_poles(basis.ptr, length(poles), poles, status) status[] == C_API.SPIR_COMPUTATION_SUCCESS || @@ -75,6 +83,19 @@ function from_IR(dlr::DiscreteLehmannRepresentation, gl::Array{T,N}, dims=1) whe output_type = T output = Array{output_type,N}(undef, output_dims...) + # Safety checks + if !_is_column_major_contiguous(gl) + error("Input array must be contiguous") + end + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") + end + + # Validate target dimension + if dims < 1 || dims > N + error("Invalid target dimension: $dims. Must be in range [1, $N]") + end + # Call appropriate C function ndim = N input_dims = Int32[size(gl)...] @@ -122,6 +143,20 @@ function to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::Array{T,N}, dims=1) wh output_type = T output = Array{output_type,N}(undef, output_dims...) + # Safety checks + if !_is_column_major_contiguous(g_dlr) + error("Input array must be contiguous") + end + if !_is_column_major_contiguous(output) + error("Output array must be contiguous") + end + + # Validate target dimension + if dims < 1 || dims > N + error("Invalid target dimension: $dims. Must be in range [1, $N]") + end + + # Call appropriate C function ndim = N input_dims = Int32[size(g_dlr)...] diff --git a/src/sampling.jl b/src/sampling.jl index 25d0272..02b4a4e 100644 --- a/src/sampling.jl +++ b/src/sampling.jl @@ -78,6 +78,9 @@ function TauSampling(basis::AbstractBasis; sampling_points=nothing, use_positive if !_is_column_major_contiguous(sampling_points) error("Sampling points must be contiguous") end + if length(sampling_points) == 0 + error("Sampling points cannot be empty") + end ptr = C_API.spir_tau_sampling_new( _get_ptr(basis), length(sampling_points), sampling_points, status) status[] == C_API.SPIR_COMPUTATION_SUCCESS || @@ -132,6 +135,11 @@ function MatsubaraSampling( # Extract indices for C API indices = [Int64(Int(p)) for p in sampling_points] + + # Safety checks + if length(indices) == 0 + error("Sampling points cannot be empty") + end status = Ref{Int32}(-100) ptr = C_API.spir_matsu_sampling_new( @@ -181,6 +189,9 @@ function evaluate( sampling::Union{TauSampling,MatsubaraSampling}, al::Array{ T,N}; dim=1) where {T,N} # Determine output dimensions + if dim < 1 || dim > N + error("dim $(dim) is invalid!") + end output_dims = collect(size(al)) output_dims[dim] = npoints(sampling) @@ -208,6 +219,9 @@ function evaluate!( output::Array{Tout,N}, sampling::TauSampling, al::Array{ Tin,N}; dim=1) where {Tout,Tin,N} # Check dimensions + if dim < 1 || dim > N + error("dim $(dim) is invalid!") + end expected_dims = collect(size(al)) expected_dims[dim] = npoints(sampling) size(output) == tuple(expected_dims...) || @@ -227,10 +241,10 @@ function evaluate!( end # Call appropriate C function based on input/output types - if Tin <: Real && Tout <: Real + if Tin == Float64 && Tout == Float64 ret = C_API.spir_sampling_eval_dd( sampling.ptr, order, ndim, input_dims, target_dim, al, output) - elseif Tin <: Complex && Tout <: Complex + elseif Tin == ComplexF64 && Tout == ComplexF64 ret = C_API.spir_sampling_eval_zz( sampling.ptr, order, ndim, input_dims, target_dim, al, output) else @@ -267,10 +281,10 @@ function evaluate!(output::Array{Tout,N}, sampling::MatsubaraSampling, end # Call appropriate C function based on input/output types - if Tin <: Real && Tout <: Complex + if Tin == Float64 && Tout == ComplexF64 ret = C_API.spir_sampling_eval_dz( sampling.ptr, order, ndim, input_dims, target_dim, al, output) - elseif Tin <: Complex && Tout <: Complex + elseif Tin == ComplexF64 && Tout == ComplexF64 ret = C_API.spir_sampling_eval_zz( sampling.ptr, order, ndim, input_dims, target_dim, al, output) else @@ -295,18 +309,20 @@ For multidimensional arrays, `dim` specifies which dimension corresponds to the function fit( sampling::Union{TauSampling,MatsubaraSampling}, al::Array{T,N}; dim=1) where { T,N} + if !(T ∈ [Float64, ComplexF64]) + error("Type combination not supported for fit: input=$T") + end # Determine output dimensions output_dims = collect(size(al)) output_dims[dim] = length(sampling.basis) - # Determine output type - typically real for coefficients + # Determine output type if sampling isa TauSampling - # For complex input, we need complex output output_type = T else # MatsubaraSampling # For Matsubara sampling, we need to be careful about type matching # The C API might expect complex output even for real input - output_type = T <: Complex ? T : ComplexF64 + output_type = ComplexF64 end output = Array{output_type,N}(undef, output_dims...) @@ -333,6 +349,15 @@ function fit!( output::Array{Tout,N}, sampling::TauSampling, al::Array{ Tin,N}; dim=1) where {Tout,Tin,N} # Check dimensions + if dim < 1 || dim > N + error("dim $(dim) is invalid!") + end + if !(Tin ∈ [Float64, ComplexF64]) + error("Type combination not supported for TauSampling fit: input=$Tin") + end + if !(Tout ∈ [Float64, ComplexF64]) + error("Type combination not supported for TauSampling fit: output=$Tout") + end expected_dims = collect(size(al)) expected_dims[dim] = length(sampling.basis) size(output) == tuple(expected_dims...) || @@ -352,10 +377,10 @@ function fit!( end # Call appropriate C function - if Tin <: Real && Tout <: Real + if Tin == Float64 && Tout == Float64 ret = C_API.spir_sampling_fit_dd( sampling.ptr, order, ndim, input_dims, target_dim, al, output) - elseif Tin <: Complex && Tout <: Complex + elseif Tin == ComplexF64 && Tout == ComplexF64 ret = C_API.spir_sampling_fit_zz( sampling.ptr, order, ndim, input_dims, target_dim, al, output) else @@ -393,12 +418,12 @@ function fit!( end # Call appropriate C function based on input/output types - if Tin <: Complex && Tout <: Complex - # Use complex-to-complex API and then extract real part if needed + if Tin == ComplexF64 && Tout == ComplexF64 ret = C_API.spir_sampling_fit_zz( sampling.ptr, order, ndim, input_dims, target_dim, al, output) - elseif Tin <: Complex && Tout <: Real + elseif Tin == ComplexF64 && Tout == Float64 # Create temporary complex output, then extract real part + # TODO: Optimize for positive_only = True temp_output = Array{ComplexF64,N}(undef, size(output)...) ret = C_API.spir_sampling_fit_zz( sampling.ptr, order, ndim, input_dims, target_dim, al, temp_output) diff --git a/test/spir/sampling_tests.jl b/test/spir/sampling_tests.jl index 602cee8..4620617 100644 --- a/test/spir/sampling_tests.jl +++ b/test/spir/sampling_tests.jl @@ -125,6 +125,7 @@ @test Gℓ_n == Gℓ_n_inplace end + #== @testset "errors with stat = $stat, $sampling" for stat in (Bosonic(), Fermionic()), sampling in (TauSampling, MatsubaraSampling) @@ -154,4 +155,5 @@ points = SparseIR.default_matsubara_sampling_points(basis) @test length(points) > 0 end + ==# end From c871a3dec9cf5b76196f2c57dc3f68009cbc1e8e Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Fri, 26 Sep 2025 12:25:40 +0900 Subject: [PATCH 07/14] Add code generation C_API.jl --- development.md | 11 +- src/C_API.jl | 773 ++++++++++++++-------------------------- utils/Project.toml | 3 + utils/generate_C_API.jl | 94 +++++ utils/generator.toml | 8 + utils/prologue.jl | 19 + 6 files changed, 395 insertions(+), 513 deletions(-) create mode 100644 utils/Project.toml create mode 100644 utils/generate_C_API.jl create mode 100644 utils/generator.toml create mode 100644 utils/prologue.jl diff --git a/development.md b/development.md index 92af899..fdc3953 100644 --- a/development.md +++ b/development.md @@ -80,16 +80,17 @@ julia --project=. --target=test -e "using Pkg; Pkg.test()" ### Code Generation -The project uses Clang.jl for generating C API bindings. To regenerate the C API: +The project uses Clang.jl for generating C API bindings. ```julia # Activate development environment -julia --project=. --target=dev - -# Run the build script -include("deps/build.jl") +cd utils +julia --project=@. -e "import Pkg; Pkg.instantiate()" +julia --project=@. generate_C_API.jl ``` +Then, `src/C_API.jl` is generated. + ### Building Documentation ```bash diff --git a/src/C_API.jl b/src/C_API.jl index 359ec06..643aba5 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -1,6 +1,7 @@ module C_API using CEnum: CEnum, @cenum + using libsparseir_jll function get_libsparseir() @@ -20,6 +21,8 @@ function get_libsparseir() end const libsparseir = get_libsparseir() + + const c_complex = ComplexF64 mutable struct _spir_kernel end @@ -91,8 +94,7 @@ function spir_sampling_release(obj) end function spir_sampling_clone(src) - ccall( - (:spir_sampling_clone, libsparseir), Ptr{spir_sampling}, (Ptr{spir_sampling},), src) + ccall((:spir_sampling_clone, libsparseir), Ptr{spir_sampling}, (Ptr{spir_sampling},), src) end function spir_sampling_is_assigned(obj) @@ -100,8 +102,7 @@ function spir_sampling_is_assigned(obj) end function _spir_sampling_get_raw_ptr(obj) - ccall( - (:_spir_sampling_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_sampling},), obj) + ccall((:_spir_sampling_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_sampling},), obj) end mutable struct _spir_sve_result end @@ -113,8 +114,7 @@ function spir_sve_result_release(obj) end function spir_sve_result_clone(src) - ccall((:spir_sve_result_clone, libsparseir), - Ptr{spir_sve_result}, (Ptr{spir_sve_result},), src) + ccall((:spir_sve_result_clone, libsparseir), Ptr{spir_sve_result}, (Ptr{spir_sve_result},), src) end function spir_sve_result_is_assigned(obj) @@ -122,8 +122,7 @@ function spir_sve_result_is_assigned(obj) end function _spir_sve_result_get_raw_ptr(obj) - ccall((:_spir_sve_result_get_raw_ptr, libsparseir), - Ptr{Cvoid}, (Ptr{spir_sve_result},), obj) + ccall((:_spir_sve_result_get_raw_ptr, libsparseir), Ptr{Cvoid}, (Ptr{spir_sve_result},), obj) end """ @@ -146,17 +145,13 @@ where ρ'(y) = w(y)ρ(y) and the weight function w(y) = 1/tanh(Λy/2) The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. # Arguments - - - `lambda`: The cutoff parameter Λ (must be non-negative) - - `status`: Pointer to store the status code - +* `lambda`: The cutoff parameter Λ (must be non-negative) +* `status`: Pointer to store the status code # Returns - Pointer to the newly created kernel object, or NULL if creation fails """ function spir_logistic_kernel_new(lambda, status) - ccall((:spir_logistic_kernel_new, libsparseir), - Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) + ccall((:spir_logistic_kernel_new, libsparseir), Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) end """ @@ -179,17 +174,13 @@ Special care is taken in evaluating this expression around y = 0 to handle the s The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. # Arguments - - - `lambda`: The cutoff parameter Λ (must be non-negative) - - `status`: Pointer to store the status code - +* `lambda`: The cutoff parameter Λ (must be non-negative) +* `status`: Pointer to store the status code # Returns - Pointer to the newly created kernel object, or NULL if creation fails """ function spir_reg_bose_kernel_new(lambda, status) - ccall((:spir_reg_bose_kernel_new, libsparseir), - Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) + ccall((:spir_reg_bose_kernel_new, libsparseir), Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) end """ @@ -204,21 +195,16 @@ This function obtains the domain boundaries (ranges) for both the x and y variab For the logistic and regularized bosonic kernels, the domain is typically [-1, 1] × [-1, 1] in dimensionless variables. # Arguments - - - `k`: Pointer to the kernel object whose domain is to be retrieved. - - `xmin`: Pointer to store the minimum value of the x-range. - - `xmax`: Pointer to store the maximum value of the x-range. - - `ymin`: Pointer to store the minimum value of the y-range. - - `ymax`: Pointer to store the maximum value of the y-range. - +* `k`: Pointer to the kernel object whose domain is to be retrieved. +* `xmin`: Pointer to store the minimum value of the x-range. +* `xmax`: Pointer to store the maximum value of the x-range. +* `ymin`: Pointer to store the minimum value of the y-range. +* `ymax`: Pointer to store the maximum value of the y-range. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_kernel_domain(k, xmin, xmax, ymin, ymax) - ccall((:spir_kernel_domain, libsparseir), Cint, - (Ptr{spir_kernel}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), - k, xmin, xmax, ymin, ymax) + ccall((:spir_kernel_domain, libsparseir), Cint, (Ptr{spir_kernel}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), k, xmin, xmax, ymin, ymax) end """ @@ -243,27 +229,20 @@ The SVE is computed by mapping it onto a singular value decomposition (SVD) of a The returned object must be freed using spir\\_release\\_sve\\_result when no longer needed # Arguments - - - `k`: Pointer to the kernel object for which to compute SVE - - `epsilon`: Accuracy target for the basis. Determines: - The relative magnitude of included singular values - The accuracy of computed singular values and vectors - - `cutoff`: Cutoff value for singular values - - `lmax`: Maximum number of Legendre polynomials to use - - `n_gauss`: Number of Gauss points for numerical integration - - `Twork`: Working data type for computations (sve). Must be one of: - [`SPIR_TWORK_FLOAT64`](@ref) (0): Use double precision (64-bit) - [`SPIR_TWORK_FLOAT64X2`](@ref) (1): Use extended precision (128-bit) - [`SPIR_TWORK_AUTO`](@ref) (-1): Automatically choose precision based on epsilon - - `status`: Pointer to store the status code - +* `k`: Pointer to the kernel object for which to compute SVE +* `epsilon`: Accuracy target for the basis. Determines: - The relative magnitude for truncation of singular values - The accuracy of computed singular values and vectors +* `cutoff`: Cutoff value for singular values. Set to -1 to use default value, i.e., 2 * √ε, where ε is the machine epsilon of the working type. +* `lmax`: Maximum number of Legendre polynomials to use +* `n_gauss`: Number of Gauss points for numerical integration +* `Twork`: Working data type for computations (sve). Must be one of: - [`SPIR_TWORK_FLOAT64`](@ref) (0): Use double precision (64-bit) - [`SPIR_TWORK_FLOAT64X2`](@ref) (1): Use extended precision (128-bit) - [`SPIR_TWORK_AUTO`](@ref) (-1): Automatically choose precision based on epsilon +* `status`: Pointer to store the status code # Returns - Pointer to the newly created SVE result, or NULL if creation fails - # See also - spir\\_release\\_sve\\_result """ function spir_sve_result_new(k, epsilon, cutoff, lmax, n_gauss, Twork, status) - ccall((:spir_sve_result_new, libsparseir), Ptr{spir_sve_result}, - (Ptr{spir_kernel}, Cdouble, Cdouble, Cint, Cint, Cint, Ptr{Cint}), - k, epsilon, cutoff, lmax, n_gauss, Twork, status) + ccall((:spir_sve_result_new, libsparseir), Ptr{spir_sve_result}, (Ptr{spir_kernel}, Cdouble, Cdouble, Cint, Cint, Cint, Ptr{Cint}), k, epsilon, cutoff, lmax, n_gauss, Twork, status) end """ @@ -274,17 +253,32 @@ Gets the number of singular values/vectors in an SVE result. This function returns the number of singular values and corresponding singular vectors contained in the specified SVE result object. This number is needed to allocate arrays of the correct size when retrieving singular values or evaluating singular vectors. # Arguments - - - `sve`: Pointer to the SVE result object - - `size`: Pointer to store the number of singular values/vectors - +* `sve`: Pointer to the SVE result object +* `size`: Pointer to store the number of singular values/vectors # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_sve_result_get_size(sve, size) - ccall((:spir_sve_result_get_size, libsparseir), Cint, - (Ptr{spir_sve_result}, Ptr{Cint}), sve, size) + ccall((:spir_sve_result_get_size, libsparseir), Cint, (Ptr{spir_sve_result}, Ptr{Cint}), sve, size) +end + +""" + spir_sve_result_truncate(sve, epsilon, max_size, status) + +Truncates an SVE result. + +This function truncates an SVE result to keep only the singular values greater than epsilon * s(0). + +# Arguments +* `sve`: Pointer to the SVE result object +* `epsilon`: Accuracy target ε (must be positive) +* `max_size`: Maximum number of basis functions to include. If -1, all +* `status`: Pointer to store the status code +# Returns +Pointer to the newly created SVE result, or NULL if creation fails +""" +function spir_sve_result_truncate(sve, epsilon, max_size, status) + ccall((:spir_sve_result_truncate, libsparseir), Ptr{spir_sve_result}, (Ptr{spir_sve_result}, Cdouble, Cint, Ptr{Cint}), sve, epsilon, max_size, status) end """ @@ -295,21 +289,15 @@ Gets the singular values from an SVE result. This function retrieves all singular values from the specified SVE result object. The singular values are stored in descending order in the output array. # Arguments - - - `sve`: Pointer to the SVE result object - - `svals`: Pre-allocated array to store the singular values. Must have size at least equal to the value returned by [`spir_sve_result_get_size`](@ref)() - +* `sve`: Pointer to the SVE result object +* `svals`: Pre-allocated array to store the singular values. Must have size at least equal to the value returned by [`spir_sve_result_get_size`](@ref)() # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sve_result_get_size`](@ref) """ function spir_sve_result_get_svals(sve, svals) - ccall((:spir_sve_result_get_svals, libsparseir), Cint, - (Ptr{spir_sve_result}, Ptr{Cdouble}), sve, svals) + ccall((:spir_sve_result_get_svals, libsparseir), Cint, (Ptr{spir_sve_result}, Ptr{Cdouble}), sve, svals) end """ @@ -320,17 +308,13 @@ Gets the number of functions in a functions object. This function returns the number of functions contained in the specified functions object. This number is needed to allocate arrays of the correct size when evaluating the functions. # Arguments - - - `funcs`: Pointer to the functions object - - `size`: Pointer to store the number of functions - +* `funcs`: Pointer to the functions object +* `size`: Pointer to store the number of functions # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_get_size(funcs, size) - ccall((:spir_funcs_get_size, libsparseir), Cint, - (Ptr{spir_funcs}, Ptr{Cint}), funcs, size) + ccall((:spir_funcs_get_size, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, size) end """ @@ -349,19 +333,15 @@ This function creates a new function object that contains only the functions spe If status is non-zero, the returned pointer will be NULL # Arguments - - - `funcs`: Pointer to the source function object - - `nslice`: Number of functions to select (length of indices array) - - `indices`: Array of indices specifying which functions to include in the slice - - `status`: Pointer to store the status code (0 for success, non-zero for error) - +* `funcs`: Pointer to the source function object +* `nslice`: Number of functions to select (length of indices array) +* `indices`: Array of indices specifying which functions to include in the slice +* `status`: Pointer to store the status code (0 for success, non-zero for error) # Returns - Pointer to the new function object containing the selected functions, or NULL on error """ function spir_funcs_get_slice(funcs, nslice, indices, status) - ccall((:spir_funcs_get_slice, libsparseir), Ptr{spir_funcs}, - (Ptr{spir_funcs}, Cint, Ptr{Cint}, Ptr{Cint}), funcs, nslice, indices, status) + ccall((:spir_funcs_get_slice, libsparseir), Ptr{spir_funcs}, (Ptr{spir_funcs}, Cint, Ptr{Cint}, Ptr{Cint}), funcs, nslice, indices, status) end """ @@ -376,18 +356,14 @@ This function evaluates all functions at a specified point x. The values of each The output array must be pre-allocated with sufficient size to store all function values # Arguments - - - `funcs`: Pointer to a functions object - - `x`: Point at which to evaluate the functions - - `out`: Pre-allocated array to store the evaluation results. - +* `funcs`: Pointer to a functions object +* `x`: Point at which to evaluate the functions +* `out`: Pre-allocated array to store the evaluation results. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_eval(funcs, x, out) - ccall((:spir_funcs_eval, libsparseir), Cint, - (Ptr{spir_funcs}, Cdouble, Ptr{Cdouble}), funcs, x, out) + ccall((:spir_funcs_eval, libsparseir), Cint, (Ptr{spir_funcs}, Cdouble, Ptr{Cdouble}), funcs, x, out) end """ @@ -398,18 +374,14 @@ Evaluate a funcs object at a single Matsubara frequency This function evaluates the basis functions at a single Matsubara frequency index. The output array will contain the values of all basis functions at the specified frequency. # Arguments - - - `funcs`: Pointer to the funcs object to evaluate - - `x`: The Matsubara frequency index (integer) - - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store all basis function values. The values are stored in the order of basis functions. - +* `funcs`: Pointer to the funcs object to evaluate +* `x`: The Matsubara frequency index (integer) +* `out`: Pointer to the output array where the results will be stored. The array must have enough space to store all basis function values. The values are stored in the order of basis functions. # Returns - int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure """ function spir_funcs_eval_matsu(funcs, x, out) - ccall((:spir_funcs_eval_matsu, libsparseir), Cint, - (Ptr{spir_funcs}, Int64, Ptr{c_complex}), funcs, x, out) + ccall((:spir_funcs_eval_matsu, libsparseir), Cint, (Ptr{spir_funcs}, Int64, Ptr{c_complex}), funcs, x, out) end """ @@ -422,21 +394,16 @@ This function evaluates the basis functions at multiple points. The points can b The output array can be stored in either row-major or column-major order, specified by the order parameter. In row-major order, the output is stored as (num\\_points, nfuncs), while in column-major order, it is stored as (nfuncs, num\\_points). # Arguments - - - `funcs`: Pointer to the funcs object to evaluate - - `order`: Memory layout of the output array: - [`SPIR_ORDER_ROW_MAJOR`](@ref): (num\\_points, nfuncs) - [`SPIR_ORDER_COLUMN_MAJOR`](@ref): (nfuncs, num\\_points) - - `num_points`: Number of points to evaluate - - `xs`: Array of points to evaluate at. The points should be in the appropriate domain (imaginary time for u basis, real frequency for v basis) - - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store num\\_points * nfuncs values, where nfuncs is the number of basis functions. - +* `funcs`: Pointer to the funcs object to evaluate +* `order`: Memory layout of the output array: - [`SPIR_ORDER_ROW_MAJOR`](@ref): (num\\_points, nfuncs) - [`SPIR_ORDER_COLUMN_MAJOR`](@ref): (nfuncs, num\\_points) +* `num_points`: Number of points to evaluate +* `xs`: Array of points to evaluate at. The points should be in the appropriate domain (imaginary time for u basis, real frequency for v basis) +* `out`: Pointer to the output array where the results will be stored. The array must have enough space to store num\\_points * nfuncs values, where nfuncs is the number of basis functions. # Returns - int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure """ function spir_funcs_batch_eval(funcs, order, num_points, xs, out) - ccall((:spir_funcs_batch_eval, libsparseir), Cint, - (Ptr{spir_funcs}, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}), - funcs, order, num_points, xs, out) + ccall((:spir_funcs_batch_eval, libsparseir), Cint, (Ptr{spir_funcs}, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}), funcs, order, num_points, xs, out) end """ @@ -451,21 +418,16 @@ This function evaluates all functions contained in a functions object at the spe The output array must be pre-allocated with sufficient size to store all function values at all requested frequencies. Indices n correspond to ωn = nπ/β, where n are odd for fermionic frequencies and even for bosonic frequencies. # Arguments - - - `funcs`: Pointer to the functions object - - `order`: Specifies the memory layout of the output array: [`SPIR_ORDER_ROW_MAJOR`](@ref) for row-major order (frequency index varies fastest), [`SPIR_ORDER_COLUMN_MAJOR`](@ref) for column-major order (function index varies fastest) - - `num_freqs`: Number of Matsubara frequencies at which to evaluate - - `matsubara_freq_indices`: Array of Matsubara frequency indices - - `out`: Pre-allocated array to store the evaluation results. The results are stored as a 2D array of size num\\_freqs x n\\_funcs. - +* `funcs`: Pointer to the functions object +* `order`: Specifies the memory layout of the output array: [`SPIR_ORDER_ROW_MAJOR`](@ref) for row-major order (frequency index varies fastest), [`SPIR_ORDER_COLUMN_MAJOR`](@ref) for column-major order (function index varies fastest) +* `num_freqs`: Number of Matsubara frequencies at which to evaluate +* `matsubara_freq_indices`: Array of Matsubara frequency indices +* `out`: Pre-allocated array to store the evaluation results. The results are stored as a 2D array of size num\\_freqs x n\\_funcs. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_batch_eval_matsu(funcs, order, num_freqs, matsubara_freq_indices, out) - ccall((:spir_funcs_batch_eval_matsu, libsparseir), Cint, - (Ptr{spir_funcs}, Cint, Cint, Ptr{Int64}, Ptr{c_complex}), - funcs, order, num_freqs, matsubara_freq_indices, out) + ccall((:spir_funcs_batch_eval_matsu, libsparseir), Cint, (Ptr{spir_funcs}, Cint, Cint, Ptr{Int64}, Ptr{c_complex}), funcs, order, num_freqs, matsubara_freq_indices, out) end """ @@ -476,17 +438,13 @@ Gets the number of roots of a funcs object. This function returns the number of roots of the specified funcs object. This function is only available for continuous functions. # Arguments - - - `funcs`: Pointer to the funcs object - - `n_roots`: Pointer to store the number of roots - +* `funcs`: Pointer to the funcs object +* `n_roots`: Pointer to store the number of roots # Returns - An integer status code: """ function spir_funcs_get_n_roots(funcs, n_roots) - ccall((:spir_funcs_get_n_roots, libsparseir), Cint, - (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_roots) + ccall((:spir_funcs_get_n_roots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_roots) end """ @@ -497,22 +455,18 @@ Gets the roots of a funcs object. This function returns the roots of the specified funcs object in the non-ascending order. If the size of the funcs object is greater than 1, the roots for all the functions are returned. This function is only available for continuous functions. # Arguments - - - `funcs`: Pointer to the funcs object - - `n_roots`: Pointer to store the number of roots - - `roots`: Pointer to store the roots - +* `funcs`: Pointer to the funcs object +* `n_roots`: Pointer to store the number of roots +* `roots`: Pointer to store the roots # Returns - An integer status code: """ function spir_funcs_get_roots(funcs, roots) - ccall((:spir_funcs_get_roots, libsparseir), Cint, - (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, roots) + ccall((:spir_funcs_get_roots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, roots) end """ - spir_basis_new(statistics, beta, omega_max, k, sve, max_size, status) + spir_basis_new(statistics, beta, omega_max, epsilon, k, sve, max_size, status) Creates a new finite temperature IR basis using a pre-computed SVE result. @@ -523,27 +477,21 @@ This function creates a intermediate representation (IR) basis using a pre-compu Using a pre-computed SVE can significantly improve performance when creating multiple basis objects with the same kernel # Arguments - - - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) - - `beta`: Inverse temperature β (must be positive) - - `omega_max`: Frequency cutoff ωmax (must be non-negative) - - `k`: Pointer to the kernel object used for the basis construction - - `sve`: Pointer to a pre-computed SVE result for the kernel - - `max_size`: Maximum number of basis functions to include. If -1, all - - `status`: Pointer to store the status code - +* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) +* `beta`: Inverse temperature β (must be positive) +* `omega_max`: Frequency cutoff ωmax (must be non-negative) +* `epsilon`: Accuracy target ε (must be positive). This parameter is used to truncate the SVE result to keep only the singular values greater than ε * s(0). +* `k`: Pointer to the kernel object used for the basis construction +* `sve`: Pointer to a pre-computed SVE result for the kernel +* `max_size`: Maximum number of basis functions to include. If -1, all +* `status`: Pointer to store the status code # Returns - Pointer to the newly created basis object, or NULL if creation fails - # See also - [`spir_sve_result_new`](@ref), spir\\_release\\_finite\\_temp\\_basis """ -function spir_basis_new(statistics, beta, omega_max, k, sve, max_size, status) - ccall((:spir_basis_new, libsparseir), Ptr{spir_basis}, - (Cint, Cdouble, Cdouble, Ptr{spir_kernel}, Ptr{spir_sve_result}, Cint, Ptr{Cint}), - statistics, beta, omega_max, k, sve, max_size, status) +function spir_basis_new(statistics, beta, omega_max, epsilon, k, sve, max_size, status) + ccall((:spir_basis_new, libsparseir), Ptr{spir_basis}, (Cint, Cdouble, Cdouble, Cdouble, Ptr{spir_kernel}, Ptr{spir_sve_result}, Cint, Ptr{Cint}), statistics, beta, omega_max, epsilon, k, sve, max_size, status) end """ @@ -562,12 +510,9 @@ This function returns the number of basis functions in the specified finite temp For a DLR basis, the size is the number of poles. # Arguments - - - `b`: Pointer to the finite temperature basis object - - `size`: Pointer to store the number of basis functions - +* `b`: Pointer to the finite temperature basis object +* `size`: Pointer to store the number of basis functions # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_basis_get_size(b, size) @@ -590,21 +535,15 @@ This function returns the singular values of the specified finite temperature ba The number of singular values is equal to the basis size # Arguments - - - `sve`: Pointer to the finite temperature basis object - - `svals`: Pointer to store the singular values - +* `sve`: Pointer to the finite temperature basis object +* `svals`: Pointer to store the singular values # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_size`](@ref) """ function spir_basis_get_svals(b, svals) - ccall((:spir_basis_get_svals, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) + ccall((:spir_basis_get_svals, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) end """ @@ -623,17 +562,13 @@ This function returns the statistics type of the specified finite temperature ba The statistics type affects the form of the basis functions and the sampling points used for evaluation. # Arguments - - - `b`: Pointer to the finite temperature basis object - - `statistics`: Pointer to store the statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) - +* `b`: Pointer to the finite temperature basis object +* `statistics`: Pointer to store the statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_basis_get_stats(b, statistics) - ccall((:spir_basis_get_stats, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cint}), b, statistics) + ccall((:spir_basis_get_stats, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, statistics) end """ @@ -644,8 +579,7 @@ Gets the singular values of a finite temperature basis. This function returns the singular values of the specified finite temperature basis object. The singular values are the square roots of the eigenvalues of the covariance matrix of the basis functions. """ function spir_basis_get_singular_values(b, svals) - ccall((:spir_basis_get_singular_values, libsparseir), - Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) + ccall((:spir_basis_get_singular_values, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) end """ @@ -660,21 +594,15 @@ This function returns an object representing the basis functions in the imaginar The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments - - - `b`: Pointer to the finite temperature basis object - - `status`: Pointer to store the status code - +* `b`: Pointer to the finite temperature basis object +* `status`: Pointer to store the status code # Returns - Pointer to the basis functions object, or NULL if creation fails - # See also - spir\\_release\\_funcs """ function spir_basis_get_u(b, status) - ccall((:spir_basis_get_u, libsparseir), Ptr{spir_funcs}, - (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_u, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -689,21 +617,15 @@ This function returns an object representing the basis functions in the real-fre The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments - - - `b`: Pointer to the finite temperature basis object - - `status`: Pointer to store the status code - +* `b`: Pointer to the finite temperature basis object +* `status`: Pointer to store the status code # Returns - Pointer to the basis functions object, or NULL if creation fails - # See also - spir\\_release\\_funcs """ function spir_basis_get_v(b, status) - ccall((:spir_basis_get_v, libsparseir), Ptr{spir_funcs}, - (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_v, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -718,21 +640,15 @@ This function returns an object representing the basis functions in the Matsubar The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments - - - `b`: Pointer to the finite temperature basis object - - `status`: Pointer to store the status code - +* `b`: Pointer to the finite temperature basis object +* `status`: Pointer to store the status code # Returns - Pointer to the basis functions object, or NULL if creation fails - # See also - spir\\_release\\_funcs """ function spir_basis_get_uhat(b, status) - ccall((:spir_basis_get_uhat, libsparseir), Ptr{spir_funcs}, - (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_uhat, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -751,21 +667,15 @@ This function returns the number of default sampling points in imaginary time ( The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `num_points`: Pointer to store the number of sampling points - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `num_points`: Pointer to store the number of sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_default_taus`](@ref) """ function spir_basis_get_n_default_taus(b, num_points) - ccall((:spir_basis_get_n_default_taus, libsparseir), - Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) + ccall((:spir_basis_get_n_default_taus, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) end """ @@ -788,21 +698,15 @@ This function fills the provided array with the default sampling points in imagi The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `points`: Pre-allocated array to store the τ sampling points - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `points`: Pre-allocated array to store the τ sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_n_default_taus`](@ref) """ function spir_basis_get_default_taus(b, points) - ccall((:spir_basis_get_default_taus, libsparseir), - Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) + ccall((:spir_basis_get_default_taus, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) end """ @@ -821,21 +725,15 @@ This function returns the number of default sampling points in real frequency ( The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `num_points`: Pointer to store the number of sampling points - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `num_points`: Pointer to store the number of sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_default_ws`](@ref) """ function spir_basis_get_n_default_ws(b, num_points) - ccall((:spir_basis_get_n_default_ws, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cint}), b, num_points) + ccall((:spir_basis_get_n_default_ws, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) end """ @@ -858,46 +756,36 @@ This function fills the provided array with the default sampling points in real The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `points`: Pre-allocated array to store the ω sampling points - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `points`: Pre-allocated array to store the ω sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_n_default_ws`](@ref) """ function spir_basis_get_default_ws(b, points) - ccall((:spir_basis_get_default_ws, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cdouble}), b, points) + ccall((:spir_basis_get_default_ws, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) end """ spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) - - -Gets the default tau sampling points for ann IR basis. - -This function returns default tau sampling points for an IR basis object. +* -# Arguments +Gets the default tau sampling points for an IR basis. - - `b`: Pointer to the basis object - - `n_points`: Number of requested sampling points. - - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. - - `n_points_returned`: Number of sampling points returned. +This function returns default tau sampling points for an IR basis object. This function is used to get more sampling points than [`spir_basis_get_n_default_taus`](@ref). +# Arguments +* `b`: Pointer to the basis object +* `n_points`: Number of requested sampling points. +* `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. +* `n_points_returned`: Number of sampling points returned. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success """ function spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) - ccall((:spir_basis_get_default_taus_ext, libsparseir), Cint, - (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), - b, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_taus_ext, libsparseir), Cint, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, n_points, points, n_points_returned) end """ @@ -916,22 +804,16 @@ This function returns the number of default sampling points in Matsubara frequen The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `positive_only`: If true, only positive frequencies are used - - `num_points`: Pointer to store the number of sampling points - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `positive_only`: If true, only positive frequencies are used +* `num_points`: Pointer to store the number of sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_default_matsus`](@ref) """ function spir_basis_get_n_default_matsus(b, positive_only, num_points) - ccall((:spir_basis_get_n_default_matsus, libsparseir), Cint, - (Ptr{spir_basis}, Bool, Ptr{Cint}), b, positive_only, num_points) + ccall((:spir_basis_get_n_default_matsus, libsparseir), Cint, (Ptr{spir_basis}, Bool, Ptr{Cint}), b, positive_only, num_points) end """ @@ -962,22 +844,16 @@ This function fills the provided array with the default sampling points in Matsu For bosonic case, the indices n give frequencies ωn = 2nπ/β # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `positive_only`: If true, only positive frequencies are used - - `points`: Pre-allocated array to store the Matsubara frequency indices - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `positive_only`: If true, only positive frequencies are used +* `points`: Pre-allocated array to store the Matsubara frequency indices # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_n_default_matsus`](@ref) """ function spir_basis_get_default_matsus(b, positive_only, points) - ccall((:spir_basis_get_default_matsus, libsparseir), Cint, - (Ptr{spir_basis}, Bool, Ptr{Int64}), b, positive_only, points) + ccall((:spir_basis_get_default_matsus, libsparseir), Cint, (Ptr{spir_basis}, Bool, Ptr{Int64}), b, positive_only, points) end """ @@ -996,23 +872,17 @@ This function returns the number of default sampling points in Matsubara frequen The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `positive_only`: If true, only positive frequencies are used - - `L`: Number of requested sampling points. - - `num_points_returned`: Pointer to store the number of sampling points returned. - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `positive_only`: If true, only positive frequencies are used +* `L`: Number of requested sampling points. +* `num_points_returned`: Pointer to store the number of sampling points returned. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_basis_get_default_matsus`](@ref) """ function spir_basis_get_n_default_matsus_ext(b, positive_only, L, num_points_returned) - ccall((:spir_basis_get_n_default_matsus_ext, libsparseir), Cint, - (Ptr{spir_basis}, Bool, Cint, Ptr{Cint}), b, positive_only, L, num_points_returned) + ccall((:spir_basis_get_n_default_matsus_ext, libsparseir), Cint, (Ptr{spir_basis}, Bool, Cint, Ptr{Cint}), b, positive_only, L, num_points_returned) end """ @@ -1023,22 +893,16 @@ Gets the default Matsubara sampling points for an IR basis. This function fills the provided array with the default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. # Arguments - - - `b`: Pointer to a finite temperature basis object (must be an IR basis) - - `positive_only`: If true, only positive frequencies are used - - `n_points`: Number of requested sampling points. - - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. - - `n_points_returned`: Number of sampling points returned. - +* `b`: Pointer to a finite temperature basis object (must be an IR basis) +* `positive_only`: If true, only positive frequencies are used +* `n_points`: Number of requested sampling points. +* `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. +* `n_points_returned`: Number of sampling points returned. # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success """ -function spir_basis_get_default_matsus_ext( - b, positive_only, n_points, points, n_points_returned) - ccall((:spir_basis_get_default_matsus_ext, libsparseir), Cint, - (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), - b, positive_only, n_points, points, n_points_returned) +function spir_basis_get_default_matsus_ext(b, positive_only, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_matsus_ext, libsparseir), Cint, (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), b, positive_only, n_points, points, n_points_returned) end """ @@ -1050,20 +914,16 @@ This function implements a variant of the discrete Lehmann representation (DLR). G(iν) = ∑ a[i] * reg[i] / (iν - w[i]) for i = 1, 2, ..., L -where: - a[i] are the expansion coefficients - w[i] are the poles on the real axis - reg[i] are the regularization factors, which are 1 for fermionic frequencies. For bosonic frequencies, we take reg[i] = tanh(βω[i]/2) (logistic kernel), reg[i] = w[i] (regularized bosonic kernel). The DLR basis functions are given by u[i](i%CE%BD) = reg[i] / (iν - w[i]) in the imaginary-frequency domain. In the imaginary-time domain, the basis functions are given by u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 + exp(-w[i]β)) for fermionic frequencies, u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 - exp(-w[i]β)) for bosonic frequencies. - iν are Matsubara frequencies +where: - a[i] are the expansion coefficients - w[i] are the poles on the real axis - reg[i] are the regularization factors, which are 1 for fermionic frequencies. For bosonic frequencies, we take reg[i] = tanh(βω[i]/2) (logistic kernel), reg[i] = w[i] (regularized bosonic kernel). The DLR basis functions are given by u[i](iν) = reg[i] / (iν - w[i]) in the imaginary-frequency domain. In the imaginary-time domain, the basis functions are given by u[i](τ) = reg[i] * exp(-w[i]τ) / (1 + exp(-w[i]β)) for fermionic frequencies, u[i](τ) = reg[i] * exp(-w[i]τ) / (1 - exp(-w[i]β)) for bosonic frequencies. - iν are Matsubara frequencies # Arguments - - - `b`: Pointer to a finite temperature basis object - - `status`: Pointer to store the status code - +* `b`: Pointer to a finite temperature basis object +* `status`: Pointer to store the status code # Returns - Pointer to the newly created DLR object, or NULL if creation fails """ function spir_dlr_new(b, status) - ccall((:spir_dlr_new, libsparseir), Ptr{spir_basis}, - (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_dlr_new, libsparseir), Ptr{spir_basis}, (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -1074,19 +934,15 @@ Creates a new Discrete Lehmann Representation (DLR) with custom poles. This function creates a DLR basis with user-specified pole locations on the real-frequency axis. This allows for more control over the pole selection compared to the automatic pole selection in [`spir_dlr_new`](@ref). # Arguments - - - `b`: Pointer to a finite temperature basis object - - `npoles`: Number of poles to use in the representation - - `poles`: Array of pole locations on the real-frequency axis - - `status`: Pointer to store the status code - +* `b`: Pointer to a finite temperature basis object +* `npoles`: Number of poles to use in the representation +* `poles`: Array of pole locations on the real-frequency axis +* `status`: Pointer to store the status code # Returns - Pointer to the newly created DLR object, or NULL if creation fails """ function spir_dlr_new_with_poles(b, npoles, poles, status) - ccall((:spir_dlr_new_with_poles, libsparseir), Ptr{spir_basis}, - (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, npoles, poles, status) + ccall((:spir_dlr_new_with_poles, libsparseir), Ptr{spir_basis}, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, npoles, poles, status) end """ @@ -1097,21 +953,15 @@ Gets the number of poles in a DLR. This function returns the number of poles in the specified DLR object. # Arguments - - - `dlr`: Pointer to the DLR object - - `num_poles`: Pointer to store the number of poles - +* `dlr`: Pointer to the DLR object +* `num_poles`: Pointer to store the number of poles # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_dlr_get_poles`](@ref) """ function spir_dlr_get_npoles(dlr, num_poles) - ccall((:spir_dlr_get_npoles, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cint}), dlr, num_poles) + ccall((:spir_dlr_get_npoles, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), dlr, num_poles) end """ @@ -1122,21 +972,15 @@ Gets the poles in a DLR. This function returns the poles in the specified DLR object. # Arguments - - - `dlr`: Pointer to the DLR object - - `poles`: Pointer to store the poles - +* `dlr`: Pointer to the DLR object +* `poles`: Pointer to store the poles # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_dlr_get_npoles`](@ref) """ function spir_dlr_get_poles(dlr, poles) - ccall((:spir_dlr_get_poles, libsparseir), Cint, - (Ptr{spir_basis}, Ptr{Cdouble}), dlr, poles) + ccall((:spir_dlr_get_poles, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), dlr, poles) end """ @@ -1149,33 +993,24 @@ Transforms a given input array from the Intermediate Representation (IR) to the The input and output arrays must be allocated with sufficient memory. The size of the input and output arrays should match the dimensions specified. The order type determines the memory layout of the input and output arrays. The function assumes that the input array is in the specified order type. The output array will be in the specified order type. # Arguments - - - `dlr`: Pointer to the DLR basis object - - `order`: Order type (C or Fortran) - - `ndim`: Number of dimensions of input/output arrays - - `input_dims`: Array of dimensions - - `target_dim`: Target dimension for the transformation (0-based) - - `input`: Input coefficients array in IR - - `out`: Output array in DLR - +* `dlr`: Pointer to the DLR basis object +* `order`: Order type (C or Fortran) +* `ndim`: Number of dimensions of input/output arrays +* `input_dims`: Array of dimensions +* `target_dim`: Target dimension for the transformation (0-based) +* `input`: Input coefficients array in IR +* `out`: Output array in DLR # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - spir\\_ir2dlr, [`spir_dlr2ir_dd`](@ref) """ function spir_ir2dlr_dd(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_ir2dlr_dd, libsparseir), Cint, - (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), - dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_dd, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), dlr, order, ndim, input_dims, target_dim, input, out) end function spir_ir2dlr_zz(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_ir2dlr_zz, libsparseir), Cint, - (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), - dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_zz, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1202,27 +1037,20 @@ where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficie The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation # Arguments - - - `dlr`: Pointer to the DLR object - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `ndim`: Number of dimensions in the input/output arrays - - `input_dims`: Array of dimension sizes - - `target_dim`: Target dimension for the transformation (0-based) - - `input`: Input array of DLR coefficients (double precision) - - `out`: Output array for the IR coefficients (double precision) - +* `dlr`: Pointer to the DLR object +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `ndim`: Number of dimensions in the input/output arrays +* `input_dims`: Array of dimension sizes +* `target_dim`: Target dimension for the transformation (0-based) +* `input`: Input array of DLR coefficients (double precision) +* `out`: Output array for the IR coefficients (double precision) # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - spir\\_ir2dlr """ function spir_dlr2ir_dd(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_dlr2ir_dd, libsparseir), Cint, - (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), - dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_dd, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1249,27 +1077,20 @@ where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficie The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation # Arguments - - - `dlr`: Pointer to the DLR object - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `ndim`: Number of dimensions in the input/output arrays - - `input_dims`: Array of dimension sizes - - `target_dim`: Target dimension for the transformation (0-based) - - `input`: Input array of DLR coefficients (complex) - - `out`: Output array for the IR coefficients (complex) - +* `dlr`: Pointer to the DLR object +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `ndim`: Number of dimensions in the input/output arrays +* `input_dims`: Array of dimension sizes +* `target_dim`: Target dimension for the transformation (0-based) +* `input`: Input array of DLR coefficients (complex) +* `out`: Output array for the IR coefficients (complex) # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_ir2dlr_zz`](@ref), [`spir_dlr2ir_dd`](@ref) """ function spir_dlr2ir_zz(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_dlr2ir_zz, libsparseir), Cint, - (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), - dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_zz, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1292,23 +1113,17 @@ Constructs a sampling object that allows transformation between the IR basis and The returned object must be freed using spir\\_release\\_sampling when no longer needed # Arguments - - - `b`: Pointer to a finite temperature basis object - - `num_points`: Number of sampling points - - `points`: Array of sampling points in imaginary time (τ) - - `status`: Pointer to store the status code - +* `b`: Pointer to a finite temperature basis object +* `num_points`: Number of sampling points +* `points`: Array of sampling points in imaginary time (τ) +* `status`: Pointer to store the status code # Returns - Pointer to the newly created sampling object, or NULL if creation fails - # See also - spir\\_release\\_sampling """ function spir_tau_sampling_new(b, num_points, points, status) - ccall((:spir_tau_sampling_new, libsparseir), Ptr{spir_sampling}, - (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, num_points, points, status) + ccall((:spir_tau_sampling_new, libsparseir), Ptr{spir_sampling}, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, num_points, points, status) end """ @@ -1319,24 +1134,18 @@ Creates a new tau sampling object for sparse sampling in imaginary time with cus This function creates a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in imaginary time (τ). The sampling points are provided by the user, allowing for custom sampling strategies. # Arguments - - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) - - `basis_size`: Basis size - - `num_points`: Number of sampling points - - `points`: Array of sampling points in imaginary time (τ) - - `matrix`: Pre-computed matrix for the sampling points (num\\_points x basis\\_size). For Matsubara sampling, this should be a complex matrix. - - `status`: Pointer to store the status code - +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) +* `basis_size`: Basis size +* `num_points`: Number of sampling points +* `points`: Array of sampling points in imaginary time (τ) +* `matrix`: Pre-computed matrix for the sampling points (num\\_points x basis\\_size). For Matsubara sampling, this should be a complex matrix. +* `status`: Pointer to store the status code # Returns - Pointer to the newly created sampling object, or NULL if creation fails """ -function spir_tau_sampling_new_with_matrix( - order, statistics, basis_size, num_points, points, matrix, status) - ccall((:spir_tau_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, - (Cint, Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}), - order, statistics, basis_size, num_points, points, matrix, status) +function spir_tau_sampling_new_with_matrix(order, statistics, basis_size, num_points, points, matrix, status) + ccall((:spir_tau_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, (Cint, Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}), order, statistics, basis_size, num_points, points, matrix, status) end """ @@ -1347,21 +1156,16 @@ Creates a new Matsubara sampling object for sparse sampling in Matsubara frequen Constructs a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in Matsubara frequencies (iωn). The sampling points are provided by the user, allowing for custom sampling strategies. # Arguments - - - `b`: Pointer to a finite temperature basis object - - `positive_only`: If true, only positive frequencies are used - - `num_points`: Number of sampling points - - `points`: Array of Matsubara frequency indices (n) for the sampling points - - `status`: Pointer to store the status code - +* `b`: Pointer to a finite temperature basis object +* `positive_only`: If true, only positive frequencies are used +* `num_points`: Number of sampling points +* `points`: Array of Matsubara frequency indices (n) for the sampling points +* `status`: Pointer to store the status code # Returns - Pointer to the newly created sampling object, or NULL if creation fails """ function spir_matsu_sampling_new(b, positive_only, num_points, points, status) - ccall((:spir_matsu_sampling_new, libsparseir), Ptr{spir_sampling}, - (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), - b, positive_only, num_points, points, status) + ccall((:spir_matsu_sampling_new, libsparseir), Ptr{spir_sampling}, (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), b, positive_only, num_points, points, status) end """ @@ -1372,29 +1176,21 @@ Creates a new Matsubara sampling object for sparse sampling in Matsubara frequen This function creates a sampling object that can be used to evaluate and fit functions at specific Matsubara frequencies. The sampling points and evaluation matrix are provided directly, allowing for custom sampling configurations. # Arguments - - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) - - `basis_size`: Basis size - - `positive_only`: If true, only positive Matsubara frequencies are used - - `num_points`: Number of sampling points - - `points`: Array of Matsubara frequencies (integer indices) - - `matrix`: Pre-computed evaluation matrix of size (num\\_points × basis\\_size) - - `status`: Pointer to store the status code - +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) +* `basis_size`: Basis size +* `positive_only`: If true, only positive Matsubara frequencies are used +* `num_points`: Number of sampling points +* `points`: Array of Matsubara frequencies (integer indices) +* `matrix`: Pre-computed evaluation matrix of size (num\\_points × basis\\_size) +* `status`: Pointer to store the status code # Returns - Pointer to the new sampling object, or NULL if creation fails - # See also - [`spir_matsu_sampling_new`](@ref) """ -function spir_matsu_sampling_new_with_matrix( - order, statistics, basis_size, positive_only, num_points, points, matrix, status) - ccall((:spir_matsu_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, - (Cint, Cint, Cint, Bool, Cint, Ptr{Int64}, Ptr{c_complex}, Ptr{Cint}), order, - statistics, basis_size, positive_only, num_points, points, matrix, status) +function spir_matsu_sampling_new_with_matrix(order, statistics, basis_size, positive_only, num_points, points, matrix, status) + ccall((:spir_matsu_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, (Cint, Cint, Cint, Bool, Cint, Ptr{Int64}, Ptr{c_complex}, Ptr{Cint}), order, statistics, basis_size, positive_only, num_points, points, matrix, status) end """ @@ -1405,21 +1201,15 @@ Gets the number of sampling points in a sampling object. This function returns the number of sampling points used in the specified sampling object. This number is needed to allocate arrays of the correct size when retrieving the actual sampling points. # Arguments - - - `s`: Pointer to the sampling object - - `num_points`: Pointer to store the number of sampling points - +* `s`: Pointer to the sampling object +* `num_points`: Pointer to store the number of sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sampling_get_taus`](@ref), [`spir_sampling_get_matsus`](@ref) """ function spir_sampling_get_npoints(s, num_points) - ccall((:spir_sampling_get_npoints, libsparseir), Cint, - (Ptr{spir_sampling}, Ptr{Cint}), s, num_points) + ccall((:spir_sampling_get_npoints, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cint}), s, num_points) end """ @@ -1434,21 +1224,15 @@ This function fills the provided array with the imaginary time (τ) sampling poi The array must be pre-allocated with size >= [`spir_sampling_get_npoints`](@ref)(s) # Arguments - - - `s`: Pointer to the sampling object - - `points`: Pre-allocated array to store the τ sampling points - +* `s`: Pointer to the sampling object +* `points`: Pre-allocated array to store the τ sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sampling_get_npoints`](@ref) """ function spir_sampling_get_taus(s, points) - ccall((:spir_sampling_get_taus, libsparseir), Cint, - (Ptr{spir_sampling}, Ptr{Cdouble}), s, points) + ccall((:spir_sampling_get_taus, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cdouble}), s, points) end """ @@ -1471,21 +1255,15 @@ This function fills the provided array with the Matsubara frequency indices (n) For bosonic case, the indices n give frequencies ωn = 2nπ/β # Arguments - - - `s`: Pointer to the sampling object - - `points`: Pre-allocated array to store the Matsubara frequency indices - +* `s`: Pointer to the sampling object +* `points`: Pre-allocated array to store the Matsubara frequency indices # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sampling_get_npoints`](@ref) """ function spir_sampling_get_matsus(s, points) - ccall((:spir_sampling_get_matsus, libsparseir), Cint, - (Ptr{spir_sampling}, Ptr{Int64}), s, points) + ccall((:spir_sampling_get_matsus, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Int64}), s, points) end """ @@ -1504,17 +1282,13 @@ This function returns the condition number of the sampling matrix used in the sp The condition number is the ratio of the largest to smallest singular value of the sampling matrix # Arguments - - - `s`: Pointer to the sampling object - - `cond_num`: Pointer to store the condition number - +* `s`: Pointer to the sampling object +* `cond_num`: Pointer to store the condition number # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_sampling_get_cond_num(s, cond_num) - ccall((:spir_sampling_get_cond_num, libsparseir), Cint, - (Ptr{spir_sampling}, Ptr{Cdouble}), s, cond_num) + ccall((:spir_sampling_get_cond_num, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cdouble}), s, cond_num) end """ @@ -1541,27 +1315,20 @@ Transforms basis coefficients to values at sampling points, where both input and The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency # Arguments - - - `s`: Pointer to the sampling object - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `ndim`: Number of dimensions in the input/output arrays - - `input_dims`: Array of dimension sizes - - `target_dim`: Target dimension for the transformation (0-based) - - `input`: Input array of basis coefficients - - `out`: Output array for the evaluated values at sampling points - +* `s`: Pointer to the sampling object +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `ndim`: Number of dimensions in the input/output arrays +* `input_dims`: Array of dimension sizes +* `target_dim`: Target dimension for the transformation (0-based) +* `input`: Input array of basis coefficients +* `out`: Output array for the evaluated values at sampling points # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sampling_eval_dz`](@ref), [`spir_sampling_eval_zz`](@ref) """ function spir_sampling_eval_dd(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_dd, libsparseir), Cint, - (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), - s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dd, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1572,13 +1339,10 @@ Evaluates basis coefficients at sampling points (double to complex version). For more details, see [`spir_sampling_eval_dd`](@ref) # See also - [`spir_sampling_eval_dd`](@ref) """ function spir_sampling_eval_dz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_dz, libsparseir), Cint, - (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{c_complex}), - s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1589,13 +1353,10 @@ Evaluates basis coefficients at sampling points (complex to complex version). For more details, see [`spir_sampling_eval_dd`](@ref) # See also - [`spir_sampling_eval_dd`](@ref) """ function spir_sampling_eval_zz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_zz, libsparseir), Cint, - (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), - s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_zz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1618,27 +1379,20 @@ Transforms values at sampling points back to basis coefficients, where both inpu The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency # Arguments - - - `s`: Pointer to the sampling object - - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) - - `ndim`: Number of dimensions in the input/output arrays - - `input_dims`: Array of dimension sizes - - `target_dim`: Target dimension for the transformation (0-based) - - `input`: Input array of values at sampling points - - `out`: Output array for the fitted basis coefficients - +* `s`: Pointer to the sampling object +* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) +* `ndim`: Number of dimensions in the input/output arrays +* `input_dims`: Array of dimension sizes +* `target_dim`: Target dimension for the transformation (0-based) +* `input`: Input array of values at sampling points +* `out`: Output array for the fitted basis coefficients # Returns - An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure - # See also - [`spir_sampling_eval_dd`](@ref), [`spir_sampling_fit_zz`](@ref) """ function spir_sampling_fit_dd(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_fit_dd, libsparseir), Cint, - (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), - s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_dd, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1649,13 +1403,10 @@ Fits values at sampling points to basis coefficients (complex to complex version For more details, see [`spir_sampling_fit_dd`](@ref) # See also - [`spir_sampling_fit_dd`](@ref) """ function spir_sampling_fit_zz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_fit_zz, libsparseir), Cint, - (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), - s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_zz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) end const SPIR_COMPUTATION_SUCCESS = 0 @@ -1688,9 +1439,15 @@ const SPIR_TWORK_FLOAT64X2 = 1 const SPIR_TWORK_AUTO = -1 +const SPIR_SVDSTRAT_FAST = 0 + +const SPIR_SVDSTRAT_ACCURATE = 1 + +const SPIR_SVDSTRAT_AUTO = -1 + const SPARSEIR_VERSION_MAJOR = 0 -const SPARSEIR_VERSION_MINOR = 4 +const SPARSEIR_VERSION_MINOR = 5 const SPARSEIR_VERSION_PATCH = 2 diff --git a/utils/Project.toml b/utils/Project.toml new file mode 100644 index 0000000..b72c81c --- /dev/null +++ b/utils/Project.toml @@ -0,0 +1,3 @@ +[deps] +Clang = "40e3b903-d033-50b4-a0cc-940c62c95e31" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/utils/generate_C_API.jl b/utils/generate_C_API.jl new file mode 100644 index 0000000..bfe0094 --- /dev/null +++ b/utils/generate_C_API.jl @@ -0,0 +1,94 @@ +using Pkg +Pkg.activate(@__DIR__) +Pkg.instantiate() + +using Clang.Generators +using Clang.LibClang.Clang_jll + +# Function to print help message +function print_help() + println("Usage: julia build.jl [OPTIONS]") + println() + println("Options:") + println(" --libsparseir-dir PATH Specify the libsparseir directory path") + println(" --help, -h Show this help message") + println() + println("Examples:") + println(" julia build.jl --libsparseir-dir /path/to/libsparseir") + println() + println("Default: Uses ../../libsparseir relative to this script") +end + +# Parse command line arguments +libsparseir_dir = nothing +for (i, arg) in enumerate(ARGS) + if arg == "--help" || arg == "-h" + print_help() + exit(0) + elseif arg == "--libsparseir-dir" + if i + 1 <= length(ARGS) + libsparseir_dir = ARGS[i + 1] + else + println("Error: --libsparseir-dir requires a path argument") + exit(1) + end + end +end + +# Get libsparseir directory from command line or use default +if libsparseir_dir === nothing + # Default path + libsparseir_dir = normpath(joinpath(@__DIR__, "../../libsparseir")) +else + # Convert to absolute path + libsparseir_dir = normpath(abspath(libsparseir_dir)) +end + +# Check if the directory exists +if !isdir(libsparseir_dir) + println("Error: libsparseir directory not found: $libsparseir_dir") + println("Please specify the correct path using --libsparseir-dir") + exit(1) +end + +include_dir = joinpath(libsparseir_dir, "include") +sparseir_dir = joinpath(include_dir, "sparseir") + +# Check if include directory exists +if !isdir(include_dir) + println("Error: include directory not found: $include_dir") + println("Please ensure the libsparseir directory contains an 'include' subdirectory") + exit(1) +end + +println("Using libsparseir directory: $libsparseir_dir") +println("Using include directory: $include_dir") + +# wrapper generator options +generator_toml = joinpath(@__DIR__, "generator.toml") +if isfile(generator_toml) + options = load_options(generator_toml) +else + println("Warning: generator.toml not found, using default options") + options = Dict{String, Any}() +end + +# add compiler flags, e.g. "-DXXXXXXXXX" +args = get_default_args() +push!(args, "-I$include_dir") + +headers = [joinpath(sparseir_dir, header) for header in readdir(sparseir_dir) if endswith(header, ".h")] +# there is also an experimental `detect_headers` function for auto-detecting top-level headers in the directory +# headers = detect_headers(sparseir_dir, args) + +# create context +ctx = create_context(headers, args, options) + +# run generator +build!(ctx) + +# Replace line 28 with: +file_path = joinpath(@__DIR__, "../src/C_API.jl") +content = read(file_path, String) +content = replace(content, "const c_complex = ComplexF32" => "const c_complex = ComplexF64") +write(file_path, content) diff --git a/utils/generator.toml b/utils/generator.toml new file mode 100644 index 0000000..e209955 --- /dev/null +++ b/utils/generator.toml @@ -0,0 +1,8 @@ +[general] +prologue_file_path = "./prologue.jl" +library_name = "libsparseir" +output_file_path = "./../src/C_API.jl" +module_name = "C_API" +# jll_pkg_name = "LibSparseIR_jll" +export_symbol_prefixes = ["spir_", "SPIR_"] +extract_c_comment_style = "doxygen" diff --git a/utils/prologue.jl b/utils/prologue.jl new file mode 100644 index 0000000..d4fb6a7 --- /dev/null +++ b/utils/prologue.jl @@ -0,0 +1,19 @@ +using libsparseir_jll + +function get_libsparseir() + # Use debug library if SPARSEIR_LIB_PATH environment variable is set + if haskey(ENV, "SPARSEIR_LIB_PATH") + debug_path = ENV["SPARSEIR_LIB_PATH"] + if isfile(debug_path) + @info "Using debug library: $debug_path" + return debug_path + else + @warn "Debug library not found at $debug_path, falling back to JLL" + end + end + + # Production: use JLL package + return libsparseir_jll.libsparseir +end + +const libsparseir = get_libsparseir() From 8125ad1bb6385577649cbee4485e49607922f3c2 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Fri, 26 Sep 2025 12:27:42 +0900 Subject: [PATCH 08/14] Require libsparseir 0.5.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d2793bb..992e236 100644 --- a/Project.toml +++ b/Project.toml @@ -19,7 +19,7 @@ LinearAlgebra = "1" QuadGK = "2.11.2" StableRNGs = "1.0.3" julia = "1.6" -libsparseir_jll = "0.4.2" +libsparseir_jll = "0.5.2" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" From a152e3086ae860deb192369fa4c8efdabe86212b Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Mon, 29 Sep 2025 06:19:40 +0900 Subject: [PATCH 09/14] Fix overlap --- src/C_API.jl | 47 ++++----- src/basis.jl | 7 +- src/poly.jl | 205 +++++++++++++++++++++++++++++++--------- test/spir/poly_tests.jl | 36 +++---- utils/prologue.jl | 23 +++-- 5 files changed, 216 insertions(+), 102 deletions(-) diff --git a/src/C_API.jl b/src/C_API.jl index 643aba5..a62c9aa 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -1,23 +1,26 @@ module C_API using CEnum: CEnum, @cenum - -using libsparseir_jll +using Libdl function get_libsparseir() # Use debug library if SPARSEIR_LIB_PATH environment variable is set if haskey(ENV, "SPARSEIR_LIB_PATH") debug_path = ENV["SPARSEIR_LIB_PATH"] - if isfile(debug_path) - @info "Using debug library: $debug_path" - return debug_path - else - @warn "Debug library not found at $debug_path, falling back to JLL" + print("SPARSEIR_LIB_PATH is set to: $debug_path") + if !isfile(debug_path) + error("Debug library not found at $debug_path") + end + try + return Libdl.LazyLibrary(debug_path) + catch e + error("Failed to load debug library: $e") end + else + # Production: use JLL package - load dynamically + @eval using libsparseir_jll + return libsparseir_jll.libsparseir end - - # Production: use JLL package - return libsparseir_jll.libsparseir end const libsparseir = get_libsparseir() @@ -431,38 +434,38 @@ function spir_funcs_batch_eval_matsu(funcs, order, num_freqs, matsubara_freq_ind end """ - spir_funcs_get_n_roots(funcs, n_roots) + spir_funcs_get_n_knots(funcs, n_knots) -Gets the number of roots of a funcs object. +Gets the number of knots of the underlying piecewise Legendre polynomial. -This function returns the number of roots of the specified funcs object. This function is only available for continuous functions. +This function returns the number of knots of the underlying piecewise Legendre polynomial. Duplicate knots are counted as one. This function is only available for continuous functions. # Arguments * `funcs`: Pointer to the funcs object -* `n_roots`: Pointer to store the number of roots +* `n_knots`: Pointer to store the number of knots # Returns An integer status code: """ -function spir_funcs_get_n_roots(funcs, n_roots) - ccall((:spir_funcs_get_n_roots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_roots) +function spir_funcs_get_n_knots(funcs, n_knots) + ccall((:spir_funcs_get_n_knots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_knots) end """ - spir_funcs_get_roots(funcs, roots) + spir_funcs_get_knots(funcs, knots) -Gets the roots of a funcs object. +Gets the knots of the underlying piecewise Legendre polynomial. -This function returns the roots of the specified funcs object in the non-ascending order. If the size of the funcs object is greater than 1, the roots for all the functions are returned. This function is only available for continuous functions. +This function returns the knots of the specified funcs object in the non-decreasing order. Duplicate knots are counted as one. The knots are returned in the non-decreasing order. # Arguments * `funcs`: Pointer to the funcs object -* `n_roots`: Pointer to store the number of roots +* `n_knots`: Pointer to store the number of knots * `roots`: Pointer to store the roots # Returns An integer status code: """ -function spir_funcs_get_roots(funcs, roots) - ccall((:spir_funcs_get_roots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, roots) +function spir_funcs_get_knots(funcs, knots) + ccall((:spir_funcs_get_knots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, knots) end """ diff --git a/src/basis.jl b/src/basis.jl index 3db04e5..69807fe 100644 --- a/src/basis.jl +++ b/src/basis.jl @@ -61,7 +61,8 @@ mutable struct FiniteTempBasis{S,K} <: AbstractBasis{S} # Create basis status = Ref{Int32}(-100) basis = SparseIR.spir_basis_new( - _statistics_to_c(S), β, ωmax, kernel.ptr, sve_result.ptr, max_size, status) + _statistics_to_c(S), β, ωmax, ε, + kernel.ptr, sve_result.ptr, max_size, status) status[] == SparseIR.SPIR_COMPUTATION_SUCCESS || error("Failed to create FiniteTempBasis $S $K $β $ωmax $ε $max_size $status[]") @@ -86,8 +87,8 @@ mutable struct FiniteTempBasis{S,K} <: AbstractBasis{S} result = new{S,K}( basis, kernel, sve_result, Float64(β), Float64(ωmax), Float64(ε), s, - PiecewiseLegendrePolyVector(u, 0.0, β), - PiecewiseLegendrePolyVector(v, -ωmax, ωmax), + PiecewiseLegendrePolyVector(u, 0.0, β, β), + PiecewiseLegendrePolyVector(v, -ωmax, ωmax, 0.0), PiecewiseLegendreFTVector(uhat) ) finalizer(b -> spir_basis_release(b.ptr), result) diff --git a/src/poly.jl b/src/poly.jl index 38214df..74b4f60 100644 --- a/src/poly.jl +++ b/src/poly.jl @@ -11,13 +11,17 @@ mutable struct PiecewiseLegendrePoly ptr::Ptr{spir_funcs} xmin::Float64 xmax::Float64 - function PiecewiseLegendrePoly(funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64) - result = new(funcs, xmin, xmax) + period::Float64 # 0.0 for a non-periodic function, the period for a periodic function + function PiecewiseLegendrePoly( + funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64, period::Float64) + result = new(funcs, xmin, xmax, period) finalizer(r -> spir_funcs_release(r.ptr), result) return result end end +Base.size(polys::PiecewiseLegendrePoly) = () + """ PiecewiseLegendrePolyVector @@ -27,14 +31,24 @@ mutable struct PiecewiseLegendrePolyVector ptr::Ptr{spir_funcs} xmin::Float64 xmax::Float64 + period::Float64 # 0.0 for a non-periodic function, the period for a periodic function function PiecewiseLegendrePolyVector( - funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64) - result = new(funcs, xmin, xmax) + funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64, period::Float64) + result = new(funcs, xmin, xmax, period) finalizer(r -> spir_funcs_release(r.ptr), result) return result end end +function Base.size(ptr::Ptr{spir_funcs}) + sz = Ref{Int32}(-1) + spir_funcs_get_size(ptr, sz) == SPIR_COMPUTATION_SUCCESS || + error("Failed to get funcs size") + return Int(sz[]) +end + +Base.size(polys::PiecewiseLegendrePolyVector) = size(polys.ptr) + """ PiecewiseLegendreFTVector @@ -47,13 +61,9 @@ function is defined as: """ mutable struct PiecewiseLegendreFTVector ptr::Ptr{spir_funcs} - xmin::Float64 - xmax::Float64 function PiecewiseLegendreFTVector(funcs::Ptr{spir_funcs}) - xmin = -1.0 - xmax = 1.0 - result = new(funcs, xmin, xmax) + result = new(funcs) finalizer(r -> spir_funcs_release(r.ptr), result) return result end @@ -94,6 +104,8 @@ function (polys::PiecewiseLegendreFTVector)(freq::MatsubaraFreq) return ret end +Base.size(polys::PiecewiseLegendreFTVector) = size(polys.ptr) + function (polys::PiecewiseLegendreFTVector)(x::AbstractVector) hcat(polys.(x)...) end @@ -108,16 +120,36 @@ function Base.getindex(funcs::Ptr{spir_funcs}, i::Int) return ret end + +function Base.getindex(funcs::Ptr{spir_funcs}, indices::Vector{Int}) + all(indices .>= 1 ) || error("Indices must be at least 1") + all(indices .<= size(funcs)) || error("Indices must be less than or equal to the size of the functions") + status = Ref{Int32}(-100) + indices_i32 = Vector{Int32}(undef, length(indices)) + indices_i32 .= indices .- 1 # Julia indices are 1-based, C indices are 0-based + ret = spir_funcs_get_slice(funcs, length(indices), indices_i32, status) + status[] == SPIR_COMPUTATION_SUCCESS || + error("Failed to get basis function for index $indices: $(status[])") + return ret +end + function Base.getindex(polys::PiecewiseLegendrePolyVector, i::Int) - return PiecewiseLegendrePoly(polys.ptr[i], polys.xmin, polys.xmax) + return PiecewiseLegendrePoly(polys.ptr[i], polys.xmin, polys.xmax, polys.period) end -# Base.getindex(funcs::Ptr{spir_funcs}, I) = [funcs[i] for i in I] -function Base.getindex(polys::PiecewiseLegendrePolyVector, I) - PiecewiseLegendrePoly[polys[i] - for i in I] + +function Base.getindex(polys::PiecewiseLegendrePolyVector, I)::Union{PiecewiseLegendrePoly, PiecewiseLegendrePolyVector} + indices = collect(1:size(polys))[I] + if indices isa Int + return PiecewiseLegendrePoly(polys.ptr[indices], polys.xmin, polys.xmax, polys.period) + elseif length(indices) == 1 + return PiecewiseLegendrePoly(polys.ptr[indices[1]], polys.xmin, polys.xmax, polys.period) + else + return PiecewiseLegendrePolyVector(polys.ptr[indices], polys.xmin, polys.xmax, polys.period) + end end + function Base.length(funcs::Ptr{spir_funcs}) sz = Ref{Int32}(-1) spir_funcs_get_size(funcs, sz) == SPIR_COMPUTATION_SUCCESS || @@ -135,56 +167,132 @@ Base.lastindex(funcs::Ptr{spir_funcs}) = length(funcs) Base.firstindex(polys::PiecewiseLegendrePolyVector) = firstindex(polys.ptr) Base.lastindex(polys::PiecewiseLegendrePolyVector) = lastindex(polys.ptr) -function roots(poly::PiecewiseLegendrePoly) - nroots_ref = Ref{Int32}(-1) - spir_funcs_get_n_roots(poly.ptr, nroots_ref) - nroots = nroots_ref[] +function knots(poly::PiecewiseLegendrePoly) + nknots_ref = Ref{Int32}(-1) + spir_funcs_get_n_knots(poly.ptr, nknots_ref) + nknots = nknots_ref[] - out = Vector{Float64}(undef, nroots) + out = Vector{Float64}(undef, nknots) - SparseIR.C_API.spir_funcs_get_roots( + SparseIR.C_API.spir_funcs_get_knots( poly.ptr, out ) return out end -function roots(poly::PiecewiseLegendrePolyVector) - nroots_ref = Ref{Int32}(-1) - spir_funcs_get_n_roots(poly.ptr, nroots_ref) - nroots = nroots_ref[] +function knots(poly::PiecewiseLegendrePolyVector) + nknots_ref = Ref{Int32}(-1) + spir_funcs_get_n_knots(poly.ptr, nknots_ref) + nknots = nknots_ref[] - out = Vector{Float64}(undef, nroots) + out = Vector{Float64}(undef, nknots) - SparseIR.C_API.spir_funcs_get_roots( + SparseIR.C_API.spir_funcs_get_knots( poly.ptr, out ) return out end -function overlap(poly::PiecewiseLegendrePolyVector, f::F) where {F} - xmin = poly.xmin - xmax = poly.xmax - pts = filter(x -> xmin ≤ x ≤ xmax, roots(poly)) - q, - _ = quadgk( - x -> poly(x) * f(x), - unique!(sort!(vcat(pts, [xmin, xmax]))); - rtol=eps(), order=10, maxevals=10^4) - q + +""" + cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, period::Float64, poly_xmin::Float64, poly_xmax::Float64) + +Generate knots that cover the integration domain, handling periodic functions. + +This function extends the basic knots to cover the entire integration domain, +taking into account periodicity if applicable. +""" +function cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, period::Float64, poly_xmin::Float64, poly_xmax::Float64) + if xmin > xmax + error("xmin must be less than xmax") + end + + # Add integration boundaries + knots_vec = unique(vcat(knots, [xmin, xmax])) + + # Handle periodic functions + if period != 0.0 + extended_knots = collect(knots_vec) + + # Extend in positive direction + i = 1 + while true + offset = i * period + new_knots = knots_vec .+ offset + if any(new_knots .> poly_xmax) + break + end + append!(extended_knots, new_knots) + i += 1 + end + + # Extend in negative direction + i = 1 + while true + offset = -i * period + new_knots = knots_vec .+ offset + if any(new_knots .< poly_xmin) + break + end + append!(extended_knots, new_knots) + i += 1 + end + + knots_vec = unique(extended_knots) + end + + # Trim knots to the integration interval + knots_vec = knots_vec[(knots_vec .>= xmin) .& (knots_vec .<= xmax)] + knots_vec = sort(knots_vec) + + return knots_vec +end + +""" + overlap(poly::PiecewiseLegendrePoly, f, xmin::Float64, xmax::Float64; + rtol=eps(), return_error=false, maxevals=10^4, points=Float64[]) + +Evaluate overlap integral of `poly` with arbitrary function `f`. + +Given the function `f`, evaluate the integral + + ∫ dx f(x) poly(x) + +using adaptive Gauss-Legendre quadrature. + +`points` is a sequence of break points in the integration interval where local +difficulties of the integrand may occur (e.g. singularities, discontinuities). +""" +function overlap( + poly::PiecewiseLegendrePoly, f::F, xmin::Float64, xmax::Float64; + rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] + ) where {F} + if xmin > xmax + error("xmin must be less than xmax") + end + knots_ = sort([xmin, xmax, points..., knots(poly)...]) + knots_ = cover_domain(knots_, xmin, xmax, poly.period, poly.xmin, poly.xmax) + + int_result, int_error = quadgk(x -> poly(x) * f(x), knots_...; + rtol, order=10, maxevals) + if return_error + return int_result, int_error + else + return int_result + end end -function overlap(poly::PiecewiseLegendrePoly, f::F) where {F} - xmin = poly.xmin - xmax = poly.xmax - pts = filter(x -> xmin ≤ x ≤ xmax, roots(poly)) - q, - _ = quadgk( - x -> poly(x) * f(x), - unique!(sort!(vcat(pts, [xmin, xmax]))); - rtol=eps(), order=10, maxevals=10^4) - return q + +function overlap( + polys::PiecewiseLegendrePolyVector, f::F, xmin::Float64, xmax::Float64; + rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] +) where {F} + result_ = [overlap(polys[i], f, xmin, xmax; rtol, return_error, maxevals, points) for i in 1:size(polys)] + result_shape = (size(polys), first(size(result_))...) + return reshape(vcat(result_...), result_shape) end + function xmin(poly::PiecewiseLegendrePoly) return poly.xmin end @@ -208,3 +316,8 @@ end function xmax(poly::PiecewiseLegendreFTVector) return poly.xmax end + +#function overlap(polys::PiecewiseLegendrePolyVector, f::F; rtol=eps(), + #return_error=false) where {F} + #return overlap.(polys, f; rtol, return_error) +#end \ No newline at end of file diff --git a/test/spir/poly_tests.jl b/test/spir/poly_tests.jl index 9a24c69..42c58ff 100644 --- a/test/spir/poly_tests.jl +++ b/test/spir/poly_tests.jl @@ -3,8 +3,8 @@ using SparseIR import SparseIR as SparseIR - β = 10 - ωmax = 4 + β = 10.0 + ωmax = 4.0 ε = 1e-6 basis = FiniteTempBasis{Fermionic}(β, ωmax, ε) @@ -13,32 +13,26 @@ @testset "u" begin @test SparseIR.xmin(basis.u) == 0.0 @test SparseIR.xmax(basis.u) == β + @test basis.u.period == β + + @test basis.u[1] isa SparseIR.PiecewiseLegendrePoly + @test basis.u[1:2] isa SparseIR.PiecewiseLegendrePolyVector + @test basis.u[2:2] isa SparseIR.PiecewiseLegendrePoly end @testset "v" begin @test SparseIR.xmin(basis.v) == -ωmax @test SparseIR.xmax(basis.v) == ωmax - end - - @testset "uhat" begin - @test SparseIR.xmin(basis.uhat) == -1.0 - @test SparseIR.xmax(basis.uhat) == 1.0 + @test basis.v.period == 0.0 end @testset "overlap" begin - ref_u = [0.27517437799713756, -0.33320547877174056, 0.20676709933869278, - -0.07612175747193344, -0.04128171229889062, - 0.09679420081388303, -0.0989389721622601, 0.06747728769454617, - -0.023901271045533704, -0.014714625415717688, - 0.03830797967004657, -0.043884983551122116, 0.034203081565519565, - -0.015618570179708689, -0.004366240169617414, 0.01933538369730581] - @test overlap(basis.u, ρ₀) ≈ ref_u - ref_v = [0.6352548229644916, -2.4069288229178198e-17, -0.22782525665797093, - -1.0842021724855044e-17, -0.1783957405542744, - -8.023096076392733e-18, 0.18907994546506804, 8.348356728138384e-18, - -0.07277201662947068, 2.3852447794681098e-18, - -0.019708084751718376, -1.919037845299343e-17, 0.05824932416884472, - -4.9873299934333204e-18, -0.05617142181206885, 1.734723475976807e-18] - @test overlap(basis.v, ρ₀) ≈ ref_v + using LinearAlgebra: I + @test isapprox(SparseIR.overlap(basis.u[1], basis.u[1], 0.0, β), 1.0, atol=1e-12) + @test isapprox(SparseIR.overlap(basis.u[1], basis.u[2], 0.0, β), 0.0, atol=1e-12) + @test isapprox(SparseIR.overlap(basis.v[1], basis.v[1], -ωmax, ωmax), 1.0, atol=1e-12) + + N = 4 + @test isapprox(SparseIR.overlap(basis.u[1:N], basis.u[1:N], 0.0, β), Matrix(I, N, N), atol=1e-12) end end diff --git a/utils/prologue.jl b/utils/prologue.jl index d4fb6a7..205c5ee 100644 --- a/utils/prologue.jl +++ b/utils/prologue.jl @@ -1,19 +1,22 @@ -using libsparseir_jll function get_libsparseir() # Use debug library if SPARSEIR_LIB_PATH environment variable is set if haskey(ENV, "SPARSEIR_LIB_PATH") debug_path = ENV["SPARSEIR_LIB_PATH"] - if isfile(debug_path) - @info "Using debug library: $debug_path" - return debug_path - else - @warn "Debug library not found at $debug_path, falling back to JLL" + print("SPARSEIR_LIB_PATH is set to: $debug_path") + if !isfile(debug_path) + error("Debug library not found at $debug_path") end + try + return Libdl.LazyLibrary(debug_path) + catch e + error("Failed to load debug library: $e") + end + else + # Production: use JLL package - load dynamically + @eval using libsparseir_jll + return libsparseir_jll.libsparseir end - - # Production: use JLL package - return libsparseir_jll.libsparseir end -const libsparseir = get_libsparseir() +const libsparseir = get_libsparseir() \ No newline at end of file From 9fafeaec6a14b8bad82c0873116a3242bdfd1d88 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Mon, 29 Sep 2025 09:10:51 +0900 Subject: [PATCH 10/14] Remove debug code --- src/poly.jl | 2 +- src/sampling.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/poly.jl b/src/poly.jl index 74b4f60..d1c2153 100644 --- a/src/poly.jl +++ b/src/poly.jl @@ -288,7 +288,7 @@ function overlap( rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] ) where {F} result_ = [overlap(polys[i], f, xmin, xmax; rtol, return_error, maxevals, points) for i in 1:size(polys)] - result_shape = (size(polys), first(size(result_))...) + result_shape = (size(polys), size(first(result_))...) return reshape(vcat(result_...), result_shape) end diff --git a/src/sampling.jl b/src/sampling.jl index 02b4a4e..ef1af7c 100644 --- a/src/sampling.jl +++ b/src/sampling.jl @@ -63,7 +63,6 @@ If `use_positive_taus=true`, the sampling points are folded to the positive tau This was the default behavior in SparseIR.jl of versions 1.x.x. """ function TauSampling(basis::AbstractBasis; sampling_points=nothing, use_positive_taus=false) - @show use_positive_taus if sampling_points === nothing points = default_tau_sampling_points(basis) if use_positive_taus From a5c1fd2aaea4350e2966b8e556e960e92fd2db6a Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 30 Sep 2025 14:01:52 +0900 Subject: [PATCH 11/14] Support libsparseir v0.6.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 992e236..88c5f59 100644 --- a/Project.toml +++ b/Project.toml @@ -19,7 +19,7 @@ LinearAlgebra = "1" QuadGK = "2.11.2" StableRNGs = "1.0.3" julia = "1.6" -libsparseir_jll = "0.5.2" +libsparseir_jll = "0.6.0" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" From 5dedcf91aef6b3fb69a9f3f0a04ea0517f7f46c8 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 30 Sep 2025 14:02:15 +0900 Subject: [PATCH 12/14] Formatted --- src/C_API.jl | 749 +++++++++++++++++++++++++----------- src/dlr.jl | 9 +- src/poly.jl | 48 ++- src/sampling.jl | 2 +- test/spir/sampling_tests.jl | 1 - utils/generate_C_API.jl | 5 +- utils/prologue.jl | 2 +- 7 files changed, 547 insertions(+), 269 deletions(-) diff --git a/src/C_API.jl b/src/C_API.jl index a62c9aa..8ae2a78 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -25,7 +25,6 @@ end const libsparseir = get_libsparseir() - const c_complex = ComplexF64 mutable struct _spir_kernel end @@ -148,13 +147,17 @@ where ρ'(y) = w(y)ρ(y) and the weight function w(y) = 1/tanh(Λy/2) The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. # Arguments -* `lambda`: The cutoff parameter Λ (must be non-negative) -* `status`: Pointer to store the status code + + - `lambda`: The cutoff parameter Λ (must be non-negative) + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created kernel object, or NULL if creation fails """ function spir_logistic_kernel_new(lambda, status) - ccall((:spir_logistic_kernel_new, libsparseir), Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) + ccall((:spir_logistic_kernel_new, libsparseir), + Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) end """ @@ -177,13 +180,17 @@ Special care is taken in evaluating this expression around y = 0 to handle the s The kernel is implemented using piecewise Legendre polynomial expansion for numerical stability and accuracy. # Arguments -* `lambda`: The cutoff parameter Λ (must be non-negative) -* `status`: Pointer to store the status code + + - `lambda`: The cutoff parameter Λ (must be non-negative) + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created kernel object, or NULL if creation fails """ function spir_reg_bose_kernel_new(lambda, status) - ccall((:spir_reg_bose_kernel_new, libsparseir), Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) + ccall((:spir_reg_bose_kernel_new, libsparseir), + Ptr{spir_kernel}, (Cdouble, Ptr{Cint}), lambda, status) end """ @@ -198,16 +205,21 @@ This function obtains the domain boundaries (ranges) for both the x and y variab For the logistic and regularized bosonic kernels, the domain is typically [-1, 1] × [-1, 1] in dimensionless variables. # Arguments -* `k`: Pointer to the kernel object whose domain is to be retrieved. -* `xmin`: Pointer to store the minimum value of the x-range. -* `xmax`: Pointer to store the maximum value of the x-range. -* `ymin`: Pointer to store the minimum value of the y-range. -* `ymax`: Pointer to store the maximum value of the y-range. + + - `k`: Pointer to the kernel object whose domain is to be retrieved. + - `xmin`: Pointer to store the minimum value of the x-range. + - `xmax`: Pointer to store the maximum value of the x-range. + - `ymin`: Pointer to store the minimum value of the y-range. + - `ymax`: Pointer to store the maximum value of the y-range. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_kernel_domain(k, xmin, xmax, ymin, ymax) - ccall((:spir_kernel_domain, libsparseir), Cint, (Ptr{spir_kernel}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), k, xmin, xmax, ymin, ymax) + ccall((:spir_kernel_domain, libsparseir), Cint, + (Ptr{spir_kernel}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), + k, xmin, xmax, ymin, ymax) end """ @@ -232,20 +244,27 @@ The SVE is computed by mapping it onto a singular value decomposition (SVD) of a The returned object must be freed using spir\\_release\\_sve\\_result when no longer needed # Arguments -* `k`: Pointer to the kernel object for which to compute SVE -* `epsilon`: Accuracy target for the basis. Determines: - The relative magnitude for truncation of singular values - The accuracy of computed singular values and vectors -* `cutoff`: Cutoff value for singular values. Set to -1 to use default value, i.e., 2 * √ε, where ε is the machine epsilon of the working type. -* `lmax`: Maximum number of Legendre polynomials to use -* `n_gauss`: Number of Gauss points for numerical integration -* `Twork`: Working data type for computations (sve). Must be one of: - [`SPIR_TWORK_FLOAT64`](@ref) (0): Use double precision (64-bit) - [`SPIR_TWORK_FLOAT64X2`](@ref) (1): Use extended precision (128-bit) - [`SPIR_TWORK_AUTO`](@ref) (-1): Automatically choose precision based on epsilon -* `status`: Pointer to store the status code + + - `k`: Pointer to the kernel object for which to compute SVE + - `epsilon`: Accuracy target for the basis. Determines: - The relative magnitude for truncation of singular values - The accuracy of computed singular values and vectors + - `cutoff`: Cutoff value for singular values. Set to -1 to use default value, i.e., 2 * √ε, where ε is the machine epsilon of the working type. + - `lmax`: Maximum number of Legendre polynomials to use + - `n_gauss`: Number of Gauss points for numerical integration + - `Twork`: Working data type for computations (sve). Must be one of: - [`SPIR_TWORK_FLOAT64`](@ref) (0): Use double precision (64-bit) - [`SPIR_TWORK_FLOAT64X2`](@ref) (1): Use extended precision (128-bit) - [`SPIR_TWORK_AUTO`](@ref) (-1): Automatically choose precision based on epsilon + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created SVE result, or NULL if creation fails + # See also + spir\\_release\\_sve\\_result """ function spir_sve_result_new(k, epsilon, cutoff, lmax, n_gauss, Twork, status) - ccall((:spir_sve_result_new, libsparseir), Ptr{spir_sve_result}, (Ptr{spir_kernel}, Cdouble, Cdouble, Cint, Cint, Cint, Ptr{Cint}), k, epsilon, cutoff, lmax, n_gauss, Twork, status) + ccall((:spir_sve_result_new, libsparseir), Ptr{spir_sve_result}, + (Ptr{spir_kernel}, Cdouble, Cdouble, Cint, Cint, Cint, Ptr{Cint}), + k, epsilon, cutoff, lmax, n_gauss, Twork, status) end """ @@ -256,13 +275,17 @@ Gets the number of singular values/vectors in an SVE result. This function returns the number of singular values and corresponding singular vectors contained in the specified SVE result object. This number is needed to allocate arrays of the correct size when retrieving singular values or evaluating singular vectors. # Arguments -* `sve`: Pointer to the SVE result object -* `size`: Pointer to store the number of singular values/vectors + + - `sve`: Pointer to the SVE result object + - `size`: Pointer to store the number of singular values/vectors + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_sve_result_get_size(sve, size) - ccall((:spir_sve_result_get_size, libsparseir), Cint, (Ptr{spir_sve_result}, Ptr{Cint}), sve, size) + ccall((:spir_sve_result_get_size, libsparseir), Cint, + (Ptr{spir_sve_result}, Ptr{Cint}), sve, size) end """ @@ -273,15 +296,19 @@ Truncates an SVE result. This function truncates an SVE result to keep only the singular values greater than epsilon * s(0). # Arguments -* `sve`: Pointer to the SVE result object -* `epsilon`: Accuracy target ε (must be positive) -* `max_size`: Maximum number of basis functions to include. If -1, all -* `status`: Pointer to store the status code + + - `sve`: Pointer to the SVE result object + - `epsilon`: Accuracy target ε (must be positive) + - `max_size`: Maximum number of basis functions to include. If -1, all + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created SVE result, or NULL if creation fails """ function spir_sve_result_truncate(sve, epsilon, max_size, status) - ccall((:spir_sve_result_truncate, libsparseir), Ptr{spir_sve_result}, (Ptr{spir_sve_result}, Cdouble, Cint, Ptr{Cint}), sve, epsilon, max_size, status) + ccall((:spir_sve_result_truncate, libsparseir), Ptr{spir_sve_result}, + (Ptr{spir_sve_result}, Cdouble, Cint, Ptr{Cint}), sve, epsilon, max_size, status) end """ @@ -292,15 +319,21 @@ Gets the singular values from an SVE result. This function retrieves all singular values from the specified SVE result object. The singular values are stored in descending order in the output array. # Arguments -* `sve`: Pointer to the SVE result object -* `svals`: Pre-allocated array to store the singular values. Must have size at least equal to the value returned by [`spir_sve_result_get_size`](@ref)() + + - `sve`: Pointer to the SVE result object + - `svals`: Pre-allocated array to store the singular values. Must have size at least equal to the value returned by [`spir_sve_result_get_size`](@ref)() + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sve_result_get_size`](@ref) """ function spir_sve_result_get_svals(sve, svals) - ccall((:spir_sve_result_get_svals, libsparseir), Cint, (Ptr{spir_sve_result}, Ptr{Cdouble}), sve, svals) + ccall((:spir_sve_result_get_svals, libsparseir), Cint, + (Ptr{spir_sve_result}, Ptr{Cdouble}), sve, svals) end """ @@ -311,13 +344,17 @@ Gets the number of functions in a functions object. This function returns the number of functions contained in the specified functions object. This number is needed to allocate arrays of the correct size when evaluating the functions. # Arguments -* `funcs`: Pointer to the functions object -* `size`: Pointer to store the number of functions + + - `funcs`: Pointer to the functions object + - `size`: Pointer to store the number of functions + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_get_size(funcs, size) - ccall((:spir_funcs_get_size, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, size) + ccall((:spir_funcs_get_size, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cint}), funcs, size) end """ @@ -336,15 +373,19 @@ This function creates a new function object that contains only the functions spe If status is non-zero, the returned pointer will be NULL # Arguments -* `funcs`: Pointer to the source function object -* `nslice`: Number of functions to select (length of indices array) -* `indices`: Array of indices specifying which functions to include in the slice -* `status`: Pointer to store the status code (0 for success, non-zero for error) + + - `funcs`: Pointer to the source function object + - `nslice`: Number of functions to select (length of indices array) + - `indices`: Array of indices specifying which functions to include in the slice + - `status`: Pointer to store the status code (0 for success, non-zero for error) + # Returns + Pointer to the new function object containing the selected functions, or NULL on error """ function spir_funcs_get_slice(funcs, nslice, indices, status) - ccall((:spir_funcs_get_slice, libsparseir), Ptr{spir_funcs}, (Ptr{spir_funcs}, Cint, Ptr{Cint}, Ptr{Cint}), funcs, nslice, indices, status) + ccall((:spir_funcs_get_slice, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_funcs}, Cint, Ptr{Cint}, Ptr{Cint}), funcs, nslice, indices, status) end """ @@ -359,14 +400,18 @@ This function evaluates all functions at a specified point x. The values of each The output array must be pre-allocated with sufficient size to store all function values # Arguments -* `funcs`: Pointer to a functions object -* `x`: Point at which to evaluate the functions -* `out`: Pre-allocated array to store the evaluation results. + + - `funcs`: Pointer to a functions object + - `x`: Point at which to evaluate the functions + - `out`: Pre-allocated array to store the evaluation results. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_eval(funcs, x, out) - ccall((:spir_funcs_eval, libsparseir), Cint, (Ptr{spir_funcs}, Cdouble, Ptr{Cdouble}), funcs, x, out) + ccall((:spir_funcs_eval, libsparseir), Cint, + (Ptr{spir_funcs}, Cdouble, Ptr{Cdouble}), funcs, x, out) end """ @@ -377,14 +422,18 @@ Evaluate a funcs object at a single Matsubara frequency This function evaluates the basis functions at a single Matsubara frequency index. The output array will contain the values of all basis functions at the specified frequency. # Arguments -* `funcs`: Pointer to the funcs object to evaluate -* `x`: The Matsubara frequency index (integer) -* `out`: Pointer to the output array where the results will be stored. The array must have enough space to store all basis function values. The values are stored in the order of basis functions. + + - `funcs`: Pointer to the funcs object to evaluate + - `x`: The Matsubara frequency index (integer) + - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store all basis function values. The values are stored in the order of basis functions. + # Returns + int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure """ function spir_funcs_eval_matsu(funcs, x, out) - ccall((:spir_funcs_eval_matsu, libsparseir), Cint, (Ptr{spir_funcs}, Int64, Ptr{c_complex}), funcs, x, out) + ccall((:spir_funcs_eval_matsu, libsparseir), Cint, + (Ptr{spir_funcs}, Int64, Ptr{c_complex}), funcs, x, out) end """ @@ -397,16 +446,21 @@ This function evaluates the basis functions at multiple points. The points can b The output array can be stored in either row-major or column-major order, specified by the order parameter. In row-major order, the output is stored as (num\\_points, nfuncs), while in column-major order, it is stored as (nfuncs, num\\_points). # Arguments -* `funcs`: Pointer to the funcs object to evaluate -* `order`: Memory layout of the output array: - [`SPIR_ORDER_ROW_MAJOR`](@ref): (num\\_points, nfuncs) - [`SPIR_ORDER_COLUMN_MAJOR`](@ref): (nfuncs, num\\_points) -* `num_points`: Number of points to evaluate -* `xs`: Array of points to evaluate at. The points should be in the appropriate domain (imaginary time for u basis, real frequency for v basis) -* `out`: Pointer to the output array where the results will be stored. The array must have enough space to store num\\_points * nfuncs values, where nfuncs is the number of basis functions. + + - `funcs`: Pointer to the funcs object to evaluate + - `order`: Memory layout of the output array: - [`SPIR_ORDER_ROW_MAJOR`](@ref): (num\\_points, nfuncs) - [`SPIR_ORDER_COLUMN_MAJOR`](@ref): (nfuncs, num\\_points) + - `num_points`: Number of points to evaluate + - `xs`: Array of points to evaluate at. The points should be in the appropriate domain (imaginary time for u basis, real frequency for v basis) + - `out`: Pointer to the output array where the results will be stored. The array must have enough space to store num\\_points * nfuncs values, where nfuncs is the number of basis functions. + # Returns + int [`SPIR_COMPUTATION_SUCCESS`](@ref) on success, or an error code on failure """ function spir_funcs_batch_eval(funcs, order, num_points, xs, out) - ccall((:spir_funcs_batch_eval, libsparseir), Cint, (Ptr{spir_funcs}, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}), funcs, order, num_points, xs, out) + ccall((:spir_funcs_batch_eval, libsparseir), Cint, + (Ptr{spir_funcs}, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + funcs, order, num_points, xs, out) end """ @@ -421,16 +475,21 @@ This function evaluates all functions contained in a functions object at the spe The output array must be pre-allocated with sufficient size to store all function values at all requested frequencies. Indices n correspond to ωn = nπ/β, where n are odd for fermionic frequencies and even for bosonic frequencies. # Arguments -* `funcs`: Pointer to the functions object -* `order`: Specifies the memory layout of the output array: [`SPIR_ORDER_ROW_MAJOR`](@ref) for row-major order (frequency index varies fastest), [`SPIR_ORDER_COLUMN_MAJOR`](@ref) for column-major order (function index varies fastest) -* `num_freqs`: Number of Matsubara frequencies at which to evaluate -* `matsubara_freq_indices`: Array of Matsubara frequency indices -* `out`: Pre-allocated array to store the evaluation results. The results are stored as a 2D array of size num\\_freqs x n\\_funcs. + + - `funcs`: Pointer to the functions object + - `order`: Specifies the memory layout of the output array: [`SPIR_ORDER_ROW_MAJOR`](@ref) for row-major order (frequency index varies fastest), [`SPIR_ORDER_COLUMN_MAJOR`](@ref) for column-major order (function index varies fastest) + - `num_freqs`: Number of Matsubara frequencies at which to evaluate + - `matsubara_freq_indices`: Array of Matsubara frequency indices + - `out`: Pre-allocated array to store the evaluation results. The results are stored as a 2D array of size num\\_freqs x n\\_funcs. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_funcs_batch_eval_matsu(funcs, order, num_freqs, matsubara_freq_indices, out) - ccall((:spir_funcs_batch_eval_matsu, libsparseir), Cint, (Ptr{spir_funcs}, Cint, Cint, Ptr{Int64}, Ptr{c_complex}), funcs, order, num_freqs, matsubara_freq_indices, out) + ccall((:spir_funcs_batch_eval_matsu, libsparseir), Cint, + (Ptr{spir_funcs}, Cint, Cint, Ptr{Int64}, Ptr{c_complex}), + funcs, order, num_freqs, matsubara_freq_indices, out) end """ @@ -441,13 +500,17 @@ Gets the number of knots of the underlying piecewise Legendre polynomial. This function returns the number of knots of the underlying piecewise Legendre polynomial. Duplicate knots are counted as one. This function is only available for continuous functions. # Arguments -* `funcs`: Pointer to the funcs object -* `n_knots`: Pointer to store the number of knots + + - `funcs`: Pointer to the funcs object + - `n_knots`: Pointer to store the number of knots + # Returns + An integer status code: """ function spir_funcs_get_n_knots(funcs, n_knots) - ccall((:spir_funcs_get_n_knots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_knots) + ccall((:spir_funcs_get_n_knots, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cint}), funcs, n_knots) end """ @@ -458,14 +521,18 @@ Gets the knots of the underlying piecewise Legendre polynomial. This function returns the knots of the specified funcs object in the non-decreasing order. Duplicate knots are counted as one. The knots are returned in the non-decreasing order. # Arguments -* `funcs`: Pointer to the funcs object -* `n_knots`: Pointer to store the number of knots -* `roots`: Pointer to store the roots + + - `funcs`: Pointer to the funcs object + - `n_knots`: Pointer to store the number of knots + - `roots`: Pointer to store the roots + # Returns + An integer status code: """ function spir_funcs_get_knots(funcs, knots) - ccall((:spir_funcs_get_knots, libsparseir), Cint, (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, knots) + ccall((:spir_funcs_get_knots, libsparseir), Cint, + (Ptr{spir_funcs}, Ptr{Cdouble}), funcs, knots) end """ @@ -480,21 +547,37 @@ This function creates a intermediate representation (IR) basis using a pre-compu Using a pre-computed SVE can significantly improve performance when creating multiple basis objects with the same kernel # Arguments -* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) -* `beta`: Inverse temperature β (must be positive) -* `omega_max`: Frequency cutoff ωmax (must be non-negative) -* `epsilon`: Accuracy target ε (must be positive). This parameter is used to truncate the SVE result to keep only the singular values greater than ε * s(0). -* `k`: Pointer to the kernel object used for the basis construction -* `sve`: Pointer to a pre-computed SVE result for the kernel -* `max_size`: Maximum number of basis functions to include. If -1, all -* `status`: Pointer to store the status code + + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `beta`: Inverse temperature β (must be positive) + - `omega_max`: Frequency cutoff ωmax (must be non-negative) + - `epsilon`: Accuracy target ε (must be positive). This parameter is used to truncate the SVE result to keep only the singular values greater than ε * s(0). + - `k`: Pointer to the kernel object used for the basis construction + - `sve`: Pointer to a pre-computed SVE result for the kernel + - `max_size`: Maximum number of basis functions to include. If -1, all + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created basis object, or NULL if creation fails + # See also + [`spir_sve_result_new`](@ref), spir\\_release\\_finite\\_temp\\_basis """ function spir_basis_new(statistics, beta, omega_max, epsilon, k, sve, max_size, status) - ccall((:spir_basis_new, libsparseir), Ptr{spir_basis}, (Cint, Cdouble, Cdouble, Cdouble, Ptr{spir_kernel}, Ptr{spir_sve_result}, Cint, Ptr{Cint}), statistics, beta, omega_max, epsilon, k, sve, max_size, status) + ccall((:spir_basis_new, libsparseir), + Ptr{spir_basis}, + (Cint, Cdouble, Cdouble, Cdouble, Ptr{spir_kernel}, + Ptr{spir_sve_result}, Cint, Ptr{Cint}), + statistics, + beta, + omega_max, + epsilon, + k, + sve, + max_size, + status) end """ @@ -513,9 +596,12 @@ This function returns the number of basis functions in the specified finite temp For a DLR basis, the size is the number of poles. # Arguments -* `b`: Pointer to the finite temperature basis object -* `size`: Pointer to store the number of basis functions + + - `b`: Pointer to the finite temperature basis object + - `size`: Pointer to store the number of basis functions + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_basis_get_size(b, size) @@ -538,15 +624,21 @@ This function returns the singular values of the specified finite temperature ba The number of singular values is equal to the basis size # Arguments -* `sve`: Pointer to the finite temperature basis object -* `svals`: Pointer to store the singular values + + - `sve`: Pointer to the finite temperature basis object + - `svals`: Pointer to store the singular values + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_size`](@ref) """ function spir_basis_get_svals(b, svals) - ccall((:spir_basis_get_svals, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) + ccall((:spir_basis_get_svals, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) end """ @@ -565,13 +657,17 @@ This function returns the statistics type of the specified finite temperature ba The statistics type affects the form of the basis functions and the sampling points used for evaluation. # Arguments -* `b`: Pointer to the finite temperature basis object -* `statistics`: Pointer to store the statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + + - `b`: Pointer to the finite temperature basis object + - `statistics`: Pointer to store the statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_basis_get_stats(b, statistics) - ccall((:spir_basis_get_stats, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, statistics) + ccall((:spir_basis_get_stats, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), b, statistics) end """ @@ -582,7 +678,8 @@ Gets the singular values of a finite temperature basis. This function returns the singular values of the specified finite temperature basis object. The singular values are the square roots of the eigenvalues of the covariance matrix of the basis functions. """ function spir_basis_get_singular_values(b, svals) - ccall((:spir_basis_get_singular_values, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) + ccall((:spir_basis_get_singular_values, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, svals) end """ @@ -597,15 +694,21 @@ This function returns an object representing the basis functions in the imaginar The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments -* `b`: Pointer to the finite temperature basis object -* `status`: Pointer to store the status code + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + # Returns + Pointer to the basis functions object, or NULL if creation fails + # See also + spir\\_release\\_funcs """ function spir_basis_get_u(b, status) - ccall((:spir_basis_get_u, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_u, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -620,15 +723,21 @@ This function returns an object representing the basis functions in the real-fre The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments -* `b`: Pointer to the finite temperature basis object -* `status`: Pointer to store the status code + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + # Returns + Pointer to the basis functions object, or NULL if creation fails + # See also + spir\\_release\\_funcs """ function spir_basis_get_v(b, status) - ccall((:spir_basis_get_v, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_v, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -643,15 +752,21 @@ This function returns an object representing the basis functions in the Matsubar The returned object must be freed using spir\\_release\\_funcs when no longer needed # Arguments -* `b`: Pointer to the finite temperature basis object -* `status`: Pointer to store the status code + + - `b`: Pointer to the finite temperature basis object + - `status`: Pointer to store the status code + # Returns + Pointer to the basis functions object, or NULL if creation fails + # See also + spir\\_release\\_funcs """ function spir_basis_get_uhat(b, status) - ccall((:spir_basis_get_uhat, libsparseir), Ptr{spir_funcs}, (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_basis_get_uhat, libsparseir), Ptr{spir_funcs}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -670,15 +785,21 @@ This function returns the number of default sampling points in imaginary time ( The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `num_points`: Pointer to store the number of sampling points + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `num_points`: Pointer to store the number of sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_default_taus`](@ref) """ function spir_basis_get_n_default_taus(b, num_points) - ccall((:spir_basis_get_n_default_taus, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) + ccall((:spir_basis_get_n_default_taus, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) end """ @@ -701,15 +822,21 @@ This function fills the provided array with the default sampling points in imagi The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `points`: Pre-allocated array to store the τ sampling points + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `points`: Pre-allocated array to store the τ sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_n_default_taus`](@ref) """ function spir_basis_get_default_taus(b, points) - ccall((:spir_basis_get_default_taus, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) + ccall((:spir_basis_get_default_taus, libsparseir), + Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) end """ @@ -728,15 +855,21 @@ This function returns the number of default sampling points in real frequency ( The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `num_points`: Pointer to store the number of sampling points + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `num_points`: Pointer to store the number of sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_default_ws`](@ref) """ function spir_basis_get_n_default_ws(b, num_points) - ccall((:spir_basis_get_n_default_ws, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), b, num_points) + ccall((:spir_basis_get_n_default_ws, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), b, num_points) end """ @@ -759,36 +892,46 @@ This function fills the provided array with the default sampling points in real The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `points`: Pre-allocated array to store the ω sampling points + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `points`: Pre-allocated array to store the ω sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_n_default_ws`](@ref) """ function spir_basis_get_default_ws(b, points) - ccall((:spir_basis_get_default_ws, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), b, points) + ccall((:spir_basis_get_default_ws, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), b, points) end """ spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) -* - + - Gets the default tau sampling points for an IR basis. This function returns default tau sampling points for an IR basis object. This function is used to get more sampling points than [`spir_basis_get_n_default_taus`](@ref). # Arguments -* `b`: Pointer to the basis object -* `n_points`: Number of requested sampling points. -* `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. -* `n_points_returned`: Number of sampling points returned. + + - `b`: Pointer to the basis object + - `n_points`: Number of requested sampling points. + - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. + - `n_points_returned`: Number of sampling points returned. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success """ function spir_basis_get_default_taus_ext(b, n_points, points, n_points_returned) - ccall((:spir_basis_get_default_taus_ext, libsparseir), Cint, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_taus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), + b, n_points, points, n_points_returned) end """ @@ -807,16 +950,22 @@ This function returns the number of default sampling points in Matsubara frequen The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `positive_only`: If true, only positive frequencies are used -* `num_points`: Pointer to store the number of sampling points + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `num_points`: Pointer to store the number of sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_default_matsus`](@ref) """ function spir_basis_get_n_default_matsus(b, positive_only, num_points) - ccall((:spir_basis_get_n_default_matsus, libsparseir), Cint, (Ptr{spir_basis}, Bool, Ptr{Cint}), b, positive_only, num_points) + ccall((:spir_basis_get_n_default_matsus, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Ptr{Cint}), b, positive_only, num_points) end """ @@ -847,16 +996,22 @@ This function fills the provided array with the default sampling points in Matsu For bosonic case, the indices n give frequencies ωn = 2nπ/β # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `positive_only`: If true, only positive frequencies are used -* `points`: Pre-allocated array to store the Matsubara frequency indices + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `points`: Pre-allocated array to store the Matsubara frequency indices + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_n_default_matsus`](@ref) """ function spir_basis_get_default_matsus(b, positive_only, points) - ccall((:spir_basis_get_default_matsus, libsparseir), Cint, (Ptr{spir_basis}, Bool, Ptr{Int64}), b, positive_only, points) + ccall((:spir_basis_get_default_matsus, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Ptr{Int64}), b, positive_only, points) end """ @@ -875,17 +1030,23 @@ This function returns the number of default sampling points in Matsubara frequen The default sampling points are chosen to provide near-optimal conditioning for the given basis size # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `positive_only`: If true, only positive frequencies are used -* `L`: Number of requested sampling points. -* `num_points_returned`: Pointer to store the number of sampling points returned. + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `L`: Number of requested sampling points. + - `num_points_returned`: Pointer to store the number of sampling points returned. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_basis_get_default_matsus`](@ref) """ function spir_basis_get_n_default_matsus_ext(b, positive_only, L, num_points_returned) - ccall((:spir_basis_get_n_default_matsus_ext, libsparseir), Cint, (Ptr{spir_basis}, Bool, Cint, Ptr{Cint}), b, positive_only, L, num_points_returned) + ccall((:spir_basis_get_n_default_matsus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Cint, Ptr{Cint}), b, positive_only, L, num_points_returned) end """ @@ -896,16 +1057,22 @@ Gets the default Matsubara sampling points for an IR basis. This function fills the provided array with the default sampling points in Matsubara frequencies (iωn) that are automatically chosen for optimal conditioning of the sampling matrix. These points are the extrema of the highest-order basis function in Matsubara frequencies. # Arguments -* `b`: Pointer to a finite temperature basis object (must be an IR basis) -* `positive_only`: If true, only positive frequencies are used -* `n_points`: Number of requested sampling points. -* `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. -* `n_points_returned`: Number of sampling points returned. + + - `b`: Pointer to a finite temperature basis object (must be an IR basis) + - `positive_only`: If true, only positive frequencies are used + - `n_points`: Number of requested sampling points. + - `points`: Pre-allocated array to store the sampling points. The size of the array must be at least n\\_points. + - `n_points_returned`: Number of sampling points returned. + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success """ -function spir_basis_get_default_matsus_ext(b, positive_only, n_points, points, n_points_returned) - ccall((:spir_basis_get_default_matsus_ext, libsparseir), Cint, (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), b, positive_only, n_points, points, n_points_returned) +function spir_basis_get_default_matsus_ext( + b, positive_only, n_points, points, n_points_returned) + ccall((:spir_basis_get_default_matsus_ext, libsparseir), Cint, + (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), + b, positive_only, n_points, points, n_points_returned) end """ @@ -917,16 +1084,20 @@ This function implements a variant of the discrete Lehmann representation (DLR). G(iν) = ∑ a[i] * reg[i] / (iν - w[i]) for i = 1, 2, ..., L -where: - a[i] are the expansion coefficients - w[i] are the poles on the real axis - reg[i] are the regularization factors, which are 1 for fermionic frequencies. For bosonic frequencies, we take reg[i] = tanh(βω[i]/2) (logistic kernel), reg[i] = w[i] (regularized bosonic kernel). The DLR basis functions are given by u[i](iν) = reg[i] / (iν - w[i]) in the imaginary-frequency domain. In the imaginary-time domain, the basis functions are given by u[i](τ) = reg[i] * exp(-w[i]τ) / (1 + exp(-w[i]β)) for fermionic frequencies, u[i](τ) = reg[i] * exp(-w[i]τ) / (1 - exp(-w[i]β)) for bosonic frequencies. - iν are Matsubara frequencies +where: - a[i] are the expansion coefficients - w[i] are the poles on the real axis - reg[i] are the regularization factors, which are 1 for fermionic frequencies. For bosonic frequencies, we take reg[i] = tanh(βω[i]/2) (logistic kernel), reg[i] = w[i] (regularized bosonic kernel). The DLR basis functions are given by u[i](i%CE%BD) = reg[i] / (iν - w[i]) in the imaginary-frequency domain. In the imaginary-time domain, the basis functions are given by u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 + exp(-w[i]β)) for fermionic frequencies, u[i](%CF%84) = reg[i] * exp(-w[i]τ) / (1 - exp(-w[i]β)) for bosonic frequencies. - iν are Matsubara frequencies # Arguments -* `b`: Pointer to a finite temperature basis object -* `status`: Pointer to store the status code + + - `b`: Pointer to a finite temperature basis object + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created DLR object, or NULL if creation fails """ function spir_dlr_new(b, status) - ccall((:spir_dlr_new, libsparseir), Ptr{spir_basis}, (Ptr{spir_basis}, Ptr{Cint}), b, status) + ccall((:spir_dlr_new, libsparseir), Ptr{spir_basis}, + (Ptr{spir_basis}, Ptr{Cint}), b, status) end """ @@ -937,15 +1108,19 @@ Creates a new Discrete Lehmann Representation (DLR) with custom poles. This function creates a DLR basis with user-specified pole locations on the real-frequency axis. This allows for more control over the pole selection compared to the automatic pole selection in [`spir_dlr_new`](@ref). # Arguments -* `b`: Pointer to a finite temperature basis object -* `npoles`: Number of poles to use in the representation -* `poles`: Array of pole locations on the real-frequency axis -* `status`: Pointer to store the status code + + - `b`: Pointer to a finite temperature basis object + - `npoles`: Number of poles to use in the representation + - `poles`: Array of pole locations on the real-frequency axis + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created DLR object, or NULL if creation fails """ function spir_dlr_new_with_poles(b, npoles, poles, status) - ccall((:spir_dlr_new_with_poles, libsparseir), Ptr{spir_basis}, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, npoles, poles, status) + ccall((:spir_dlr_new_with_poles, libsparseir), Ptr{spir_basis}, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, npoles, poles, status) end """ @@ -956,15 +1131,21 @@ Gets the number of poles in a DLR. This function returns the number of poles in the specified DLR object. # Arguments -* `dlr`: Pointer to the DLR object -* `num_poles`: Pointer to store the number of poles + + - `dlr`: Pointer to the DLR object + - `num_poles`: Pointer to store the number of poles + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_dlr_get_poles`](@ref) """ function spir_dlr_get_npoles(dlr, num_poles) - ccall((:spir_dlr_get_npoles, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cint}), dlr, num_poles) + ccall((:spir_dlr_get_npoles, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cint}), dlr, num_poles) end """ @@ -975,15 +1156,21 @@ Gets the poles in a DLR. This function returns the poles in the specified DLR object. # Arguments -* `dlr`: Pointer to the DLR object -* `poles`: Pointer to store the poles + + - `dlr`: Pointer to the DLR object + - `poles`: Pointer to store the poles + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_dlr_get_npoles`](@ref) """ function spir_dlr_get_poles(dlr, poles) - ccall((:spir_dlr_get_poles, libsparseir), Cint, (Ptr{spir_basis}, Ptr{Cdouble}), dlr, poles) + ccall((:spir_dlr_get_poles, libsparseir), Cint, + (Ptr{spir_basis}, Ptr{Cdouble}), dlr, poles) end """ @@ -996,24 +1183,33 @@ Transforms a given input array from the Intermediate Representation (IR) to the The input and output arrays must be allocated with sufficient memory. The size of the input and output arrays should match the dimensions specified. The order type determines the memory layout of the input and output arrays. The function assumes that the input array is in the specified order type. The output array will be in the specified order type. # Arguments -* `dlr`: Pointer to the DLR basis object -* `order`: Order type (C or Fortran) -* `ndim`: Number of dimensions of input/output arrays -* `input_dims`: Array of dimensions -* `target_dim`: Target dimension for the transformation (0-based) -* `input`: Input coefficients array in IR -* `out`: Output array in DLR + + - `dlr`: Pointer to the DLR basis object + - `order`: Order type (C or Fortran) + - `ndim`: Number of dimensions of input/output arrays + - `input_dims`: Array of dimensions + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input coefficients array in IR + - `out`: Output array in DLR + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + spir\\_ir2dlr, [`spir_dlr2ir_dd`](@ref) """ function spir_ir2dlr_dd(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_ir2dlr_dd, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_dd, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + dlr, order, ndim, input_dims, target_dim, input, out) end function spir_ir2dlr_zz(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_ir2dlr_zz, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_ir2dlr_zz, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1040,20 +1236,27 @@ where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficie The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation # Arguments -* `dlr`: Pointer to the DLR object -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `ndim`: Number of dimensions in the input/output arrays -* `input_dims`: Array of dimension sizes -* `target_dim`: Target dimension for the transformation (0-based) -* `input`: Input array of DLR coefficients (double precision) -* `out`: Output array for the IR coefficients (double precision) + + - `dlr`: Pointer to the DLR object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of DLR coefficients (double precision) + - `out`: Output array for the IR coefficients (double precision) + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + spir\\_ir2dlr """ function spir_dlr2ir_dd(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_dlr2ir_dd, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_dd, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1080,20 +1283,27 @@ where: - g\\_IR are the coefficients in the IR basis - g\\_DLR are the coefficie The transformation is a direct matrix multiplication, which is typically faster than the inverse transformation # Arguments -* `dlr`: Pointer to the DLR object -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `ndim`: Number of dimensions in the input/output arrays -* `input_dims`: Array of dimension sizes -* `target_dim`: Target dimension for the transformation (0-based) -* `input`: Input array of DLR coefficients (complex) -* `out`: Output array for the IR coefficients (complex) + + - `dlr`: Pointer to the DLR object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of DLR coefficients (complex) + - `out`: Output array for the IR coefficients (complex) + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_ir2dlr_zz`](@ref), [`spir_dlr2ir_dd`](@ref) """ function spir_dlr2ir_zz(dlr, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_dlr2ir_zz, libsparseir), Cint, (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), dlr, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_dlr2ir_zz, libsparseir), Cint, + (Ptr{spir_basis}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + dlr, order, ndim, input_dims, target_dim, input, out) end """ @@ -1116,17 +1326,23 @@ Constructs a sampling object that allows transformation between the IR basis and The returned object must be freed using spir\\_release\\_sampling when no longer needed # Arguments -* `b`: Pointer to a finite temperature basis object -* `num_points`: Number of sampling points -* `points`: Array of sampling points in imaginary time (τ) -* `status`: Pointer to store the status code + + - `b`: Pointer to a finite temperature basis object + - `num_points`: Number of sampling points + - `points`: Array of sampling points in imaginary time (τ) + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created sampling object, or NULL if creation fails + # See also + spir\\_release\\_sampling """ function spir_tau_sampling_new(b, num_points, points, status) - ccall((:spir_tau_sampling_new, libsparseir), Ptr{spir_sampling}, (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, num_points, points, status) + ccall((:spir_tau_sampling_new, libsparseir), Ptr{spir_sampling}, + (Ptr{spir_basis}, Cint, Ptr{Cdouble}, Ptr{Cint}), b, num_points, points, status) end """ @@ -1137,18 +1353,24 @@ Creates a new tau sampling object for sparse sampling in imaginary time with cus This function creates a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in imaginary time (τ). The sampling points are provided by the user, allowing for custom sampling strategies. # Arguments -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) -* `basis_size`: Basis size -* `num_points`: Number of sampling points -* `points`: Array of sampling points in imaginary time (τ) -* `matrix`: Pre-computed matrix for the sampling points (num\\_points x basis\\_size). For Matsubara sampling, this should be a complex matrix. -* `status`: Pointer to store the status code + + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `basis_size`: Basis size + - `num_points`: Number of sampling points + - `points`: Array of sampling points in imaginary time (τ) + - `matrix`: Pre-computed matrix for the sampling points (num\\_points x basis\\_size). For Matsubara sampling, this should be a complex matrix. + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created sampling object, or NULL if creation fails """ -function spir_tau_sampling_new_with_matrix(order, statistics, basis_size, num_points, points, matrix, status) - ccall((:spir_tau_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, (Cint, Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}), order, statistics, basis_size, num_points, points, matrix, status) +function spir_tau_sampling_new_with_matrix( + order, statistics, basis_size, num_points, points, matrix, status) + ccall((:spir_tau_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, + (Cint, Cint, Cint, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}), + order, statistics, basis_size, num_points, points, matrix, status) end """ @@ -1159,16 +1381,21 @@ Creates a new Matsubara sampling object for sparse sampling in Matsubara frequen Constructs a sampling object that allows transformation between the IR basis and a user-specified set of sampling points in Matsubara frequencies (iωn). The sampling points are provided by the user, allowing for custom sampling strategies. # Arguments -* `b`: Pointer to a finite temperature basis object -* `positive_only`: If true, only positive frequencies are used -* `num_points`: Number of sampling points -* `points`: Array of Matsubara frequency indices (n) for the sampling points -* `status`: Pointer to store the status code + + - `b`: Pointer to a finite temperature basis object + - `positive_only`: If true, only positive frequencies are used + - `num_points`: Number of sampling points + - `points`: Array of Matsubara frequency indices (n) for the sampling points + - `status`: Pointer to store the status code + # Returns + Pointer to the newly created sampling object, or NULL if creation fails """ function spir_matsu_sampling_new(b, positive_only, num_points, points, status) - ccall((:spir_matsu_sampling_new, libsparseir), Ptr{spir_sampling}, (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), b, positive_only, num_points, points, status) + ccall((:spir_matsu_sampling_new, libsparseir), Ptr{spir_sampling}, + (Ptr{spir_basis}, Bool, Cint, Ptr{Int64}, Ptr{Cint}), + b, positive_only, num_points, points, status) end """ @@ -1179,21 +1406,29 @@ Creates a new Matsubara sampling object for sparse sampling in Matsubara frequen This function creates a sampling object that can be used to evaluate and fit functions at specific Matsubara frequencies. The sampling points and evaluation matrix are provided directly, allowing for custom sampling configurations. # Arguments -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) -* `basis_size`: Basis size -* `positive_only`: If true, only positive Matsubara frequencies are used -* `num_points`: Number of sampling points -* `points`: Array of Matsubara frequencies (integer indices) -* `matrix`: Pre-computed evaluation matrix of size (num\\_points × basis\\_size) -* `status`: Pointer to store the status code + + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `statistics`: Statistics type ([`SPIR_STATISTICS_FERMIONIC`](@ref) or [`SPIR_STATISTICS_BOSONIC`](@ref)) + - `basis_size`: Basis size + - `positive_only`: If true, only positive Matsubara frequencies are used + - `num_points`: Number of sampling points + - `points`: Array of Matsubara frequencies (integer indices) + - `matrix`: Pre-computed evaluation matrix of size (num\\_points × basis\\_size) + - `status`: Pointer to store the status code + # Returns + Pointer to the new sampling object, or NULL if creation fails + # See also + [`spir_matsu_sampling_new`](@ref) """ -function spir_matsu_sampling_new_with_matrix(order, statistics, basis_size, positive_only, num_points, points, matrix, status) - ccall((:spir_matsu_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, (Cint, Cint, Cint, Bool, Cint, Ptr{Int64}, Ptr{c_complex}, Ptr{Cint}), order, statistics, basis_size, positive_only, num_points, points, matrix, status) +function spir_matsu_sampling_new_with_matrix( + order, statistics, basis_size, positive_only, num_points, points, matrix, status) + ccall((:spir_matsu_sampling_new_with_matrix, libsparseir), Ptr{spir_sampling}, + (Cint, Cint, Cint, Bool, Cint, Ptr{Int64}, Ptr{c_complex}, Ptr{Cint}), order, + statistics, basis_size, positive_only, num_points, points, matrix, status) end """ @@ -1204,15 +1439,21 @@ Gets the number of sampling points in a sampling object. This function returns the number of sampling points used in the specified sampling object. This number is needed to allocate arrays of the correct size when retrieving the actual sampling points. # Arguments -* `s`: Pointer to the sampling object -* `num_points`: Pointer to store the number of sampling points + + - `s`: Pointer to the sampling object + - `num_points`: Pointer to store the number of sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sampling_get_taus`](@ref), [`spir_sampling_get_matsus`](@ref) """ function spir_sampling_get_npoints(s, num_points) - ccall((:spir_sampling_get_npoints, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cint}), s, num_points) + ccall((:spir_sampling_get_npoints, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cint}), s, num_points) end """ @@ -1227,15 +1468,21 @@ This function fills the provided array with the imaginary time (τ) sampling poi The array must be pre-allocated with size >= [`spir_sampling_get_npoints`](@ref)(s) # Arguments -* `s`: Pointer to the sampling object -* `points`: Pre-allocated array to store the τ sampling points + + - `s`: Pointer to the sampling object + - `points`: Pre-allocated array to store the τ sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sampling_get_npoints`](@ref) """ function spir_sampling_get_taus(s, points) - ccall((:spir_sampling_get_taus, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cdouble}), s, points) + ccall((:spir_sampling_get_taus, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cdouble}), s, points) end """ @@ -1258,15 +1505,21 @@ This function fills the provided array with the Matsubara frequency indices (n) For bosonic case, the indices n give frequencies ωn = 2nπ/β # Arguments -* `s`: Pointer to the sampling object -* `points`: Pre-allocated array to store the Matsubara frequency indices + + - `s`: Pointer to the sampling object + - `points`: Pre-allocated array to store the Matsubara frequency indices + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sampling_get_npoints`](@ref) """ function spir_sampling_get_matsus(s, points) - ccall((:spir_sampling_get_matsus, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Int64}), s, points) + ccall((:spir_sampling_get_matsus, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Int64}), s, points) end """ @@ -1285,13 +1538,17 @@ This function returns the condition number of the sampling matrix used in the sp The condition number is the ratio of the largest to smallest singular value of the sampling matrix # Arguments -* `s`: Pointer to the sampling object -* `cond_num`: Pointer to store the condition number + + - `s`: Pointer to the sampling object + - `cond_num`: Pointer to store the condition number + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure """ function spir_sampling_get_cond_num(s, cond_num) - ccall((:spir_sampling_get_cond_num, libsparseir), Cint, (Ptr{spir_sampling}, Ptr{Cdouble}), s, cond_num) + ccall((:spir_sampling_get_cond_num, libsparseir), Cint, + (Ptr{spir_sampling}, Ptr{Cdouble}), s, cond_num) end """ @@ -1318,20 +1575,27 @@ Transforms basis coefficients to values at sampling points, where both input and The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency # Arguments -* `s`: Pointer to the sampling object -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `ndim`: Number of dimensions in the input/output arrays -* `input_dims`: Array of dimension sizes -* `target_dim`: Target dimension for the transformation (0-based) -* `input`: Input array of basis coefficients -* `out`: Output array for the evaluated values at sampling points + + - `s`: Pointer to the sampling object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of basis coefficients + - `out`: Output array for the evaluated values at sampling points + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sampling_eval_dz`](@ref), [`spir_sampling_eval_zz`](@ref) """ function spir_sampling_eval_dd(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_dd, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dd, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1342,10 +1606,13 @@ Evaluates basis coefficients at sampling points (double to complex version). For more details, see [`spir_sampling_eval_dd`](@ref) # See also + [`spir_sampling_eval_dd`](@ref) """ function spir_sampling_eval_dz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_dz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_dz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1356,10 +1623,13 @@ Evaluates basis coefficients at sampling points (complex to complex version). For more details, see [`spir_sampling_eval_dd`](@ref) # See also + [`spir_sampling_eval_dd`](@ref) """ function spir_sampling_eval_zz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_eval_zz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_eval_zz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1382,20 +1652,27 @@ Transforms values at sampling points back to basis coefficients, where both inpu The transformation is performed using a pre-computed sampling matrix that is factorized using SVD for efficiency # Arguments -* `s`: Pointer to the sampling object -* `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) -* `ndim`: Number of dimensions in the input/output arrays -* `input_dims`: Array of dimension sizes -* `target_dim`: Target dimension for the transformation (0-based) -* `input`: Input array of values at sampling points -* `out`: Output array for the fitted basis coefficients + + - `s`: Pointer to the sampling object + - `order`: Memory layout order ([`SPIR_ORDER_ROW_MAJOR`](@ref) or [`SPIR_ORDER_COLUMN_MAJOR`](@ref)) + - `ndim`: Number of dimensions in the input/output arrays + - `input_dims`: Array of dimension sizes + - `target_dim`: Target dimension for the transformation (0-based) + - `input`: Input array of values at sampling points + - `out`: Output array for the fitted basis coefficients + # Returns + An integer status code: - 0 ([`SPIR_COMPUTATION_SUCCESS`](@ref)) on success - A non-zero error code on failure + # See also + [`spir_sampling_eval_dd`](@ref), [`spir_sampling_fit_zz`](@ref) """ function spir_sampling_fit_dd(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_fit_dd, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_dd, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), + s, order, ndim, input_dims, target_dim, input, out) end """ @@ -1406,10 +1683,13 @@ Fits values at sampling points to basis coefficients (complex to complex version For more details, see [`spir_sampling_fit_dd`](@ref) # See also + [`spir_sampling_fit_dd`](@ref) """ function spir_sampling_fit_zz(s, order, ndim, input_dims, target_dim, input, out) - ccall((:spir_sampling_fit_zz, libsparseir), Cint, (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), s, order, ndim, input_dims, target_dim, input, out) + ccall((:spir_sampling_fit_zz, libsparseir), Cint, + (Ptr{spir_sampling}, Cint, Cint, Ptr{Cint}, Cint, Ptr{c_complex}, Ptr{c_complex}), + s, order, ndim, input_dims, target_dim, input, out) end const SPIR_COMPUTATION_SUCCESS = 0 @@ -1457,6 +1737,7 @@ const SPARSEIR_VERSION_PATCH = 2 # exports const PREFIXES = ["spir_", "SPIR_"] for name in names(@__MODULE__; all=true), prefix in PREFIXES + if startswith(string(name), prefix) @eval export $name end diff --git a/src/dlr.jl b/src/dlr.jl index d229885..2c66354 100644 --- a/src/dlr.jl +++ b/src/dlr.jl @@ -45,7 +45,7 @@ function DiscreteLehmannRepresentation( if length(poles) == 0 error("Poles array cannot be empty") end - + status = Ref{Int32}(-100) dlr_ptr = C_API.spir_dlr_new_with_poles(basis.ptr, length(poles), poles, status) status[] == C_API.SPIR_COMPUTATION_SUCCESS || @@ -90,12 +90,12 @@ function from_IR(dlr::DiscreteLehmannRepresentation, gl::Array{T,N}, dims=1) whe if !_is_column_major_contiguous(output) error("Output array must be contiguous") end - + # Validate target dimension if dims < 1 || dims > N error("Invalid target dimension: $dims. Must be in range [1, $N]") end - + # Call appropriate C function ndim = N input_dims = Int32[size(gl)...] @@ -150,12 +150,11 @@ function to_IR(dlr::DiscreteLehmannRepresentation, g_dlr::Array{T,N}, dims=1) wh if !_is_column_major_contiguous(output) error("Output array must be contiguous") end - + # Validate target dimension if dims < 1 || dims > N error("Invalid target dimension: $dims. Must be in range [1, $N]") end - # Call appropriate C function ndim = N diff --git a/src/poly.jl b/src/poly.jl index d1c2153..a1da494 100644 --- a/src/poly.jl +++ b/src/poly.jl @@ -13,7 +13,7 @@ mutable struct PiecewiseLegendrePoly xmax::Float64 period::Float64 # 0.0 for a non-periodic function, the period for a periodic function function PiecewiseLegendrePoly( - funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64, period::Float64) + funcs::Ptr{spir_funcs}, xmin::Float64, xmax::Float64, period::Float64) result = new(funcs, xmin, xmax, period) finalizer(r -> spir_funcs_release(r.ptr), result) return result @@ -120,10 +120,10 @@ function Base.getindex(funcs::Ptr{spir_funcs}, i::Int) return ret end - function Base.getindex(funcs::Ptr{spir_funcs}, indices::Vector{Int}) - all(indices .>= 1 ) || error("Indices must be at least 1") - all(indices .<= size(funcs)) || error("Indices must be less than or equal to the size of the functions") + all(indices .>= 1) || error("Indices must be at least 1") + all(indices .<= size(funcs)) || + error("Indices must be less than or equal to the size of the functions") status = Ref{Int32}(-100) indices_i32 = Vector{Int32}(undef, length(indices)) indices_i32 .= indices .- 1 # Julia indices are 1-based, C indices are 0-based @@ -137,8 +137,8 @@ function Base.getindex(polys::PiecewiseLegendrePolyVector, i::Int) return PiecewiseLegendrePoly(polys.ptr[i], polys.xmin, polys.xmax, polys.period) end - -function Base.getindex(polys::PiecewiseLegendrePolyVector, I)::Union{PiecewiseLegendrePoly, PiecewiseLegendrePolyVector} +function Base.getindex(polys::PiecewiseLegendrePolyVector, + I)::Union{PiecewiseLegendrePoly,PiecewiseLegendrePolyVector} indices = collect(1:size(polys))[I] if indices isa Int return PiecewiseLegendrePoly(polys.ptr[indices], polys.xmin, polys.xmax, polys.period) @@ -149,7 +149,6 @@ function Base.getindex(polys::PiecewiseLegendrePolyVector, I)::Union{PiecewiseLe end end - function Base.length(funcs::Ptr{spir_funcs}) sz = Ref{Int32}(-1) spir_funcs_get_size(funcs, sz) == SPIR_COMPUTATION_SUCCESS || @@ -193,7 +192,6 @@ function knots(poly::PiecewiseLegendrePolyVector) return out end - """ cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, period::Float64, poly_xmin::Float64, poly_xmax::Float64) @@ -202,18 +200,19 @@ Generate knots that cover the integration domain, handling periodic functions. This function extends the basic knots to cover the entire integration domain, taking into account periodicity if applicable. """ -function cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, period::Float64, poly_xmin::Float64, poly_xmax::Float64) +function cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, + period::Float64, poly_xmin::Float64, poly_xmax::Float64) if xmin > xmax error("xmin must be less than xmax") end - + # Add integration boundaries knots_vec = unique(vcat(knots, [xmin, xmax])) - + # Handle periodic functions if period != 0.0 extended_knots = collect(knots_vec) - + # Extend in positive direction i = 1 while true @@ -225,7 +224,7 @@ function cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, peri append!(extended_knots, new_knots) i += 1 end - + # Extend in negative direction i = 1 while true @@ -237,14 +236,14 @@ function cover_domain(knots::Vector{Float64}, xmin::Float64, xmax::Float64, peri append!(extended_knots, new_knots) i += 1 end - + knots_vec = unique(extended_knots) end - + # Trim knots to the integration interval knots_vec = knots_vec[(knots_vec .>= xmin) .& (knots_vec .<= xmax)] knots_vec = sort(knots_vec) - + return knots_vec end @@ -266,7 +265,7 @@ difficulties of the integrand may occur (e.g. singularities, discontinuities). function overlap( poly::PiecewiseLegendrePoly, f::F, xmin::Float64, xmax::Float64; rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] - ) where {F} +) where {F} if xmin > xmax error("xmin must be less than xmax") end @@ -282,17 +281,16 @@ function overlap( end end - function overlap( - polys::PiecewiseLegendrePolyVector, f::F, xmin::Float64, xmax::Float64; - rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] + polys::PiecewiseLegendrePolyVector, f::F, xmin::Float64, xmax::Float64; + rtol=eps(), return_error=false, maxevals=10^4, points=Float64[] ) where {F} - result_ = [overlap(polys[i], f, xmin, xmax; rtol, return_error, maxevals, points) for i in 1:size(polys)] + result_ = [overlap(polys[i], f, xmin, xmax; rtol, return_error, maxevals, points) + for i in 1:size(polys)] result_shape = (size(polys), size(first(result_))...) return reshape(vcat(result_...), result_shape) end - function xmin(poly::PiecewiseLegendrePoly) return poly.xmin end @@ -318,6 +316,6 @@ function xmax(poly::PiecewiseLegendreFTVector) end #function overlap(polys::PiecewiseLegendrePolyVector, f::F; rtol=eps(), - #return_error=false) where {F} - #return overlap.(polys, f; rtol, return_error) -#end \ No newline at end of file +#return_error=false) where {F} +#return overlap.(polys, f; rtol, return_error) +#end diff --git a/src/sampling.jl b/src/sampling.jl index ef1af7c..42ab53c 100644 --- a/src/sampling.jl +++ b/src/sampling.jl @@ -134,7 +134,7 @@ function MatsubaraSampling( # Extract indices for C API indices = [Int64(Int(p)) for p in sampling_points] - + # Safety checks if length(indices) == 0 error("Sampling points cannot be empty") diff --git a/test/spir/sampling_tests.jl b/test/spir/sampling_tests.jl index 4620617..1d59599 100644 --- a/test/spir/sampling_tests.jl +++ b/test/spir/sampling_tests.jl @@ -88,7 +88,6 @@ @testset "iω noise with stat = $stat, Λ = $Λ" for stat in (Bosonic(), Fermionic()), Λ in (10, 42), positive_only in (false, true) - sve_logistic = SparseIR.SVEResult(LogisticKernel(Λ), 1e-10) basis = FiniteTempBasis(stat, 1, Λ, 1e-10; sve_result=sve_logistic) smpl = MatsubaraSampling(basis; positive_only) diff --git a/utils/generate_C_API.jl b/utils/generate_C_API.jl index bfe0094..df1df8c 100644 --- a/utils/generate_C_API.jl +++ b/utils/generate_C_API.jl @@ -70,14 +70,15 @@ if isfile(generator_toml) options = load_options(generator_toml) else println("Warning: generator.toml not found, using default options") - options = Dict{String, Any}() + options = Dict{String,Any}() end # add compiler flags, e.g. "-DXXXXXXXXX" args = get_default_args() push!(args, "-I$include_dir") -headers = [joinpath(sparseir_dir, header) for header in readdir(sparseir_dir) if endswith(header, ".h")] +headers = [joinpath(sparseir_dir, header) + for header in readdir(sparseir_dir) if endswith(header, ".h")] # there is also an experimental `detect_headers` function for auto-detecting top-level headers in the directory # headers = detect_headers(sparseir_dir, args) diff --git a/utils/prologue.jl b/utils/prologue.jl index 205c5ee..af6c5b5 100644 --- a/utils/prologue.jl +++ b/utils/prologue.jl @@ -19,4 +19,4 @@ function get_libsparseir() end end -const libsparseir = get_libsparseir() \ No newline at end of file +const libsparseir = get_libsparseir() From ec3a4d39b9d654d303562a0eae5b65a63d193940 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 30 Sep 2025 14:09:42 +0900 Subject: [PATCH 13/14] 2.0.0a1 to 2.0.0-alpha.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 88c5f59..4cd0147 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SparseIR" uuid = "4fe2279e-80f0-4adb-8463-ee114ff56b7d" authors = ["SatoshiTerasaki ", "Samuel Badr ", "Hiroshi Shinaoka ", "Markus Wallerberger "] -version = "2.0.0a1" +version = "2.0.0-alpha.1" [deps] CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82" From ba7f22562b0f6361d2a2e49504b68211a5bee42f Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Tue, 30 Sep 2025 14:27:24 +0900 Subject: [PATCH 14/14] Disabled tests on Windows --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d6912af..24b3ee9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -40,9 +40,9 @@ jobs: os: macOS-latest arch: arm64 # libsparseir is not yet tested on Windows - - version: 'lts' - os: windows-latest - arch: x64 + #- version: 'lts' + #os: windows-latest + #arch: x64 #- version: '1' #os: windows-latest #arch: x64