oefpil_sys/
lib.rs

1//! Rust FFI bindings to statically linked [C/Fortran library] OEFPIL
2//!
3//! [C/Fortran library]: https://gitlab.com/cmi6014/oefpil
4//!
5//! For a safe API, see the [`oefpil`](https://docs.rs/oefpil) crate.
6//!
7//! # System Requirements
8//!
9//! By default, this crate dynamically links to the runtime dependency LAPACK and requires a C
10//! compiler as build dependency. With the `built-in` feature enabled (marked with ☑ in the table
11//! below), a subset of LAPACK and its dependency BLAS shipped with this crate is compiled and
12//! statically linked. This eliminates the runtime dependency LAPACK but requires the GCC Fortran
13//! compiler as build dependency which itself depends on and complements the GCC C compiler such
14//! that GCC can compile both C and Fortran sources. It is attempted to statically link the
15//! dependencies of the subset (i.e, the GNU Fortran runtime library and the GCC quad-precision math
16//! library) whereas dynamic linking serves as fallback if no static libraries are found. The
17//! required runtime and build dependencies are satisfied by installing following system packages
18//! where "or" as in `|` has higher precedence than "and" as in `,`:
19//!
20//! | Operating System | `built-in` | Runtime Dependencies | Build Dependencies            |
21//! |------------------|:----------:|----------------------|-------------------------------|
22//! | Debian Trixie    | ☐          | `liblapack3`         | `gcc \| clang, liblapack-dev` |
23//! | Debian Trixie    | ☑          |                 | `gfortran`                    |
24//! | Fedora Linux     | ☐          | `lapack`             | `gcc \| clang, lapack-devel`  |
25//! | Fedora Linux     | ☑          |                 | `gcc-gfortran`                |
26//! | Arch Linux       | ☐          | `lapack`             | `gcc \| clang, lapack`        |
27//! | Arch Linux       | ☑          |                 | `gcc-fortran`                 |
28//!
29//! # Overview
30//!
31//! The main function of interest is [`oefpil`]. Among other arguments, it expects the convergence
32//! [`Criterion`], log [`Verbosity`], log [`FILE`] (e.g., [`stdout_file`], [`stderr_file`]), and a
33//! covariance matrix tiled by variables. Among its data fields, a tiled covariance matrix (TCM)
34//! comprises metadata about its tilemap and tiling [`Mode`]. The tilemap encodes via
35//! [`Mode::Diagonal`] or [`Mode::Full`] which tiles are diagonal or block tiles. The number of
36//! `samples` and `variables` define the number of fields per tile and the number of tiles per
37//! covariance matrix. A diagonal tile stores `samples` fields whereas a block tile stores
38//! `samples.pow(2)` fields. The tiling mode encodes where the tiles are and whether their mode is
39//! restricted to be diagonal. For each tiling mode, there are different sets of methods for
40//! allocating the tilemap and the data fields and for setting the data fields per tile.
41//!
42//!   * [`Mode::Diagonal`]: Diagonal tiles on the diagonal.
43//!       * [`oefpil_tilemap_diagtiles_new`]: Allocates tilemap.
44//!       * [`oefpil_tcm_diag_new`]: Allocates fields.
45//!       * [`oefpil_tcm_diag_set_tile_diag`]: Sets fields per diagonal tile.
46//!   * [`Mode::BlockDiagonal`]: Diagonal or block tiles on the diagonal.
47//!       * [`oefpil_tilemap_diagtiles_new`]: Allocates tilemap.
48//!       * [`oefpil_tcm_blockdiag_new`]: Allocates fields.
49//!       * [`oefpil_tcm_blockdiag_set_tile_diag`]: Sets fields per diagonal tile.
50//!       * [`oefpil_tcm_blockdiag_set_tile_half`]: Sets fields per block tile (row-major
51//!         [triangular slice] of lower triangle).
52//!       * [`oefpil_tcm_blockdiag_set_tile_full`]: Sets fields per block tile (row-major slice).
53//!   * [`Mode::Diagonals`]: Diagonal tiles all over.
54//!       * [`oefpil_tilemap_alltiles_new`]: Allocates tilemap.
55//!       * [`oefpil_tcm_diags_new`]: Allocates fields.
56//!       * [`oefpil_tcm_diags_set_tile_diag`]: Sets fields per diagonal tile.
57//!   * [`Mode::Full`]: Diagonal or block tiles all over.
58//!       * [`oefpil_tilemap_alltiles_new`]: Allocates tilemap.
59//!       * [`oefpil_tcm_full_new`]: Allocates fields.
60//!       * [`oefpil_tcm_full_set_tile_diag`]: Sets fields per diagonal tile.
61//!       * [`oefpil_tcm_full_set_tile_half`]: Sets fields per block tile (row-major [triangular
62//!         slice] of lower triangle).
63//!       * [`oefpil_tcm_full_set_tile_full`]: Sets fields per block tile (row-major slice).
64//!
65//! [triangular slice]: https://en.wikipedia.org/wiki/Triangular_array
66
67pub use libc;
68
69use core::ffi::{c_int, c_long, c_void};
70use libc::FILE;
71
72/// Function pointer type passed to [`oefpil`] as 1st argument.
73///
74/// Arguments:
75///
76///   * `data`: User-defined structure defining model inclusive number of variables and parameters.
77///   * `samples`: Number of samples per variable.
78///   * `x`: Sample from independent variables (sample-major).
79///   * `p`: Parameters.
80///   * `fx`: Evaluated dependent variables.
81///   * `dfdx`: Evaluated derivatives in independent variables (sample-major).
82///   * `dfdp`: Evaluated derivatives in parameters (sample-major).
83pub type Evaluate = Option<
84    unsafe extern "C" fn(
85        data: *mut c_void,
86        samples: c_int,
87        x: *const f64,
88        p: *const f64,
89        fx: *mut f64,
90        dfdx: *mut f64,
91        dfdp: *mut f64,
92    ),
93>;
94
95/// Mode of tiled covariance matrix or mode of tile as part of tilemap.
96///
97/// Valid modes of tile as part of tilemap are:
98///
99///   * [`Self::None`] for an unset tile,
100///   * [`Self::Diagonal`] for a diagonal tile, and
101///   * [`Self::Full`] for a block tile.
102///
103/// Default is [`Self::None`].
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
105pub enum Mode {
106    /// Mode of unset tile.
107    #[default]
108    None = 0,
109    /// Mode of covariance matrix with diagonal tiles on its diagonal (or mode of diagonal tile).
110    Diagonal = 1,
111    /// Mode of covariance matrix with diagonal or block tiles on its diagonal.
112    BlockDiagonal = 2,
113    /// Mode of covariance matrix with diagonal tiles all over.
114    Diagonals = 3,
115    /// Mode of covariance matrix with diagonal or block tiles all over (or mode of block tile).
116    Full = 4,
117}
118
119/// Convergence criterion.
120///
121/// Default is [`Self::RelPOrAbsPAndRelXOrAbsX`].
122#[derive(Debug, Clone, Copy, Default)]
123#[non_exhaustive]
124pub enum Criterion {
125    /// Convergence in relative change of parameter mean.
126    RelP = 0,
127    /// Convergence in [`Self::RelP`] or absolute value of parameter mean.
128    RelPOrAbsP = 1,
129    /// Convergence in relative change of independent variable mean.
130    RelX = 2,
131    /// Convergence in [`Self::RelX`] or absolute value of independent variable mean.
132    RelXOrAbsX = 3,
133    /// Convergence in [`Self::RelP`] and [`Self::RelX`].
134    RelPAndRelX = 4,
135    /// Convergence in [`Self::RelPOrAbsP`] and [`Self::RelXOrAbsX`].
136    #[default]
137    RelPOrAbsPAndRelXOrAbsX = 5,
138    /// Convergence in chi-squared.
139    ChiSquared = 6,
140}
141
142/// Log verbosity.
143///
144/// Default is [`Self::Silent`].
145#[derive(Debug, Clone, Copy, Default)]
146pub enum Verbosity {
147    /// No logging.
148    #[default]
149    Silent = 0,
150    /// Logs parameter mean.
151    ParameterMean = 1,
152    /// Logs covariance matrix among [`Self::ParameterMean`].
153    ParameterCovariance = 2,
154    /// Logs individual steps among [`Self::ParameterMean`] and [`Self::ParameterCovariance`].
155    IndividualSteps = 3,
156}
157
158/// Computes the p-value `q` for `chisq` and `nu` degrees of freedom.
159///
160/// # Safety
161///
162/// This function is safe as long as the pointers are valid.
163#[doc(hidden)]
164#[unsafe(no_mangle)]
165pub unsafe extern "C" fn dcdchi(chisq: f64, nu: f64, p: *mut f64, q: *mut f64, ierr: *mut c_long) {
166    unsafe {
167        dgami(0.5 * nu, 0.5 * chisq, p, q, ierr);
168    }
169}
170
171/// Computes the incomplete gamma function ratios `pans` and `qans` at `a` and `x`.
172///
173/// # Safety
174///
175/// This function is safe as long as the pointers are valid.
176#[doc(hidden)]
177#[unsafe(no_mangle)]
178pub unsafe extern "C" fn dgami(a: f64, x: f64, pans: *mut f64, qans: *mut f64, ierr: *mut c_long) {
179    unsafe {
180        (*pans, *ierr) = if x >= 0.0 && a > 0.0 {
181            (special::Gamma::inc_gamma(x, a), false.into())
182        } else {
183            (f64::NAN, true.into())
184        };
185        *qans = 1.0 - *pans;
186    }
187}
188
189unsafe extern "C" {
190    /// Returns the standard input file.
191    pub safe fn stdin_file() -> *mut FILE;
192    /// Returns the standard output file.
193    pub safe fn stdout_file() -> *mut FILE;
194    /// Returns the standard error file.
195    pub safe fn stderr_file() -> *mut FILE;
196
197    /// Computes the Cholesky factorization of a real symmetric positive definite matrix.
198    #[doc(hidden)]
199    pub unsafe fn dpotrf(uplo: *const i8, n: c_int, a: *mut f64, lda: c_int, info: *mut c_int);
200
201    /// Multiplies lower or upper triangular matrix with vector.
202    #[doc(hidden)]
203    pub unsafe fn dtrmv(
204        uplo: *const i8,
205        transa: *const i8,
206        diag: *const i8,
207        n: c_int,
208        a: *const f64,
209        lda: c_int,
210        x: *mut f64,
211        incx: c_int,
212    );
213
214    /// Creates initialized tilemap for [`oefpil_tcm_diag_new`] or [`oefpil_tcm_blockdiag_new`].
215    ///
216    /// Arguments:
217    ///
218    ///   * Number of `variables` or tiles.
219    pub safe fn oefpil_tilemap_diagtiles_new(variables: c_int) -> *mut c_int;
220
221    /// Creates tiled covariance matrix of diagonal tiles on its diagonal.
222    ///
223    /// Arguments:
224    ///
225    ///   * Number of `samples` per variable.
226    ///   * Number of `variables` or tiles.
227    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
228    pub unsafe fn oefpil_tcm_diag_new(
229        samples: c_int,
230        variables: c_int,
231        map: *mut c_int,
232    ) -> *mut f64;
233    /// Sets `fields` of diagonal tile of `tcm` created with [`oefpil_tcm_diag_new`].
234    ///
235    /// Arguments:
236    ///
237    ///   * Number of `samples` per variable.
238    ///   * Number of `variables` or tiles.
239    ///   * Tiled covariance matrix `tcm` created with
240    ///     <code>[oefpil_tcm_diag_new](samples, variables, map)</code>.
241    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
242    ///   * Tile `row_column` in `0..variables`.
243    ///   * Tile `fields` to copy into diagonal tile at `row_column` of `tcm`. Number of `fields` is
244    ///     `samples`.
245    pub unsafe fn oefpil_tcm_diag_set_tile_diag(
246        samples: c_int,
247        variables: c_int,
248        tcm: *mut f64,
249        map: *mut c_int,
250        row_column: c_int,
251        fields: *const f64,
252    );
253
254    /// Creates tiled covariance matrix of diagonal or block tiles on its diagonal.
255    ///
256    /// Arguments:
257    ///
258    ///   * Number of `samples` per variable.
259    ///   * Number of `variables` or tiles.
260    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
261    pub unsafe fn oefpil_tcm_blockdiag_new(
262        samples: c_int,
263        variables: c_int,
264        map: *mut c_int,
265    ) -> *mut f64;
266    /// Sets `fields` of diagonal tile of `tcm` created with [`oefpil_tcm_blockdiag_new`].
267    ///
268    /// Arguments:
269    ///
270    ///   * Number of `samples` per variable.
271    ///   * Number of `variables` or tiles.
272    ///   * Tiled covariance matrix `tcm` created with
273    ///     <code>[oefpil_tcm_blockdiag_new](samples, variables, map)</code>.
274    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
275    ///   * Tile `row_column` in `0..variables`.
276    ///   * Tile `fields` to copy into diagonal tile at `row_column` of `tcm`. Number of `fields` is
277    ///     `samples`.
278    pub unsafe fn oefpil_tcm_blockdiag_set_tile_diag(
279        samples: c_int,
280        variables: c_int,
281        tcm: *mut f64,
282        map: *mut c_int,
283        row_column: c_int,
284        fields: *const f64,
285    );
286    /// Sets `fields` of symmetric block tile of `tcm` created with [`oefpil_tcm_blockdiag_new`].
287    ///
288    /// Arguments:
289    ///
290    ///   * Number of `samples` per variable.
291    ///   * Number of `variables` or tiles.
292    ///   * Tiled covariance matrix `tcm` created with
293    ///     <code>[oefpil_tcm_blockdiag_new](samples, variables, map)</code>.
294    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
295    ///   * Tile `row_column` in `0..variables`.
296    ///   * Tile `fields` to copy into symmetric block tile at `row_column` of `tcm`. Number of
297    ///     `fields` is `samples * (samples + 1) / 2`. `fields` is a row-major [triangular slice] of
298    ///     the lower triangle where the upper triangle is automatically constructed.
299    ///
300    /// [triangular slice]: https://en.wikipedia.org/wiki/Triangular_array
301    pub unsafe fn oefpil_tcm_blockdiag_set_tile_half(
302        samples: c_int,
303        variables: c_int,
304        tcm: *mut f64,
305        map: *mut c_int,
306        row_column: c_int,
307        fields: *const f64,
308    );
309    /// Sets `fields` of symmetric block tile of `tcm` created with [`oefpil_tcm_blockdiag_new`].
310    ///
311    /// Arguments:
312    ///
313    ///   * Number of `samples` per variable.
314    ///   * Number of `variables` or tiles.
315    ///   * Tiled covariance matrix `tcm` created with
316    ///     <code>[oefpil_tcm_blockdiag_new](samples, variables, map)</code>.
317    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
318    ///   * Tile `row_column` in `0..variables`.
319    ///   * Tile `fields` to copy into symmetric block tile at `row_column` of `tcm`. Number of
320    ///     `fields` is `samples.pow(2)`. As the block tile is on the diagonal, `fields` must be
321    ///     symmetric (not validated) where row-major and column-major order coincide.
322    pub unsafe fn oefpil_tcm_blockdiag_set_tile_full(
323        samples: c_int,
324        variables: c_int,
325        tcm: *mut f64,
326        map: *mut c_int,
327        row_column: c_int,
328        fields: *const f64,
329    );
330
331    /// Creates initialized tilemap for [`oefpil_tcm_diags_new`] or [`oefpil_tcm_full_new`].
332    ///
333    /// Arguments:
334    ///
335    ///   * Number of `variables` or tiles.
336    pub safe fn oefpil_tilemap_alltiles_new(bn: c_int) -> *mut c_int;
337
338    /// Creates tiled covariance matrix of diagonal tiles.
339    ///
340    /// Arguments:
341    ///
342    ///   * Number of `samples` per variable.
343    ///   * Number of `variables` or tiles.
344    ///   * Tile `map` created with <code>[oefpil_tilemap_alltiles_new]\(variables\)</code>.
345    pub unsafe fn oefpil_tcm_diags_new(
346        samples: c_int,
347        variables: c_int,
348        map: *mut c_int,
349    ) -> *mut f64;
350    /// Sets `fields` of diagonal tile of `tcm` created with [`oefpil_tcm_diags_new`].
351    ///
352    /// Arguments:
353    ///
354    ///   * Number of `samples` per variable.
355    ///   * Number of `variables` or tiles.
356    ///   * Tiled covariance matrix `tcm` created with
357    ///     <code>[oefpil_tcm_diags_new](samples, variables, map)</code>.
358    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
359    ///   * Tile `row` in `0..variables`.
360    ///   * Tile `column` in `0..variables`.
361    ///   * Tile `fields` to copy into diagonal tile at `row` and `column` of `tcm`. Number of
362    ///     `fields` is `samples`.
363    pub unsafe fn oefpil_tcm_diags_set_tile_diag(
364        samples: c_int,
365        variables: c_int,
366        tcm: *mut f64,
367        map: *mut c_int,
368        row: c_int,
369        column: c_int,
370        fields: *const f64,
371    );
372
373    /// Creates tiled covariance matrix of diagonal or block tiles.
374    ///
375    /// Arguments:
376    ///
377    ///   * Number of `samples` per variable.
378    ///   * Number of `variables` or tiles.
379    ///   * Tile `map` created with <code>[oefpil_tilemap_alltiles_new]\(variables\)</code>.
380    pub unsafe fn oefpil_tcm_full_new(
381        samples: c_int,
382        variables: c_int,
383        map: *mut c_int,
384    ) -> *mut f64;
385    /// Sets `fields` of diagonal tile of `tcm` created with [`oefpil_tcm_blockdiag_new`].
386    ///
387    /// Arguments:
388    ///
389    ///   * Number of `samples` per variable.
390    ///   * Number of `variables` or tiles.
391    ///   * Tiled covariance matrix `tcm` created with
392    ///     <code>[oefpil_tcm_blockdiag_new](samples, variables, map)</code>.
393    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
394    ///   * Tile `row` in `0..variables`.
395    ///   * Tile `column` in `0..variables`.
396    ///   * Tile `fields` to copy into diagonal tile at `row` and `column` of `tcm`. Number of
397    ///     `fields` is `samples`.
398    pub unsafe fn oefpil_tcm_full_set_tile_diag(
399        samples: c_int,
400        variables: c_int,
401        tcm: *mut f64,
402        map: *mut c_int,
403        row: c_int,
404        column: c_int,
405        fields: *const f64,
406    );
407    /// Sets `fields` of symmetric block tile of `tcm` created with [`oefpil_tcm_blockdiag_new`].
408    ///
409    /// Arguments:
410    ///
411    ///   * Number of `samples` per variable.
412    ///   * Number of `variables` or tiles.
413    ///   * Tiled covariance matrix `tcm` created with
414    ///     <code>[oefpil_tcm_blockdiag_new](samples, variables, map)</code>.
415    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
416    ///   * Tile `row` in `0..variables`.
417    ///   * Tile `column` in `0..variables`.
418    ///   * Tile `fields` to copy into symmetric block tile at `row_column` of `tcm`. Number of
419    ///     `fields` is `samples * (samples + 1) / 2`. `fields` is a row-major [triangular slice] of
420    ///     the lower triangle where the upper triangle is automatically constructed.
421    ///
422    /// [triangular slice]: https://en.wikipedia.org/wiki/Triangular_array
423    pub unsafe fn oefpil_tcm_full_set_tile_half(
424        samples: c_int,
425        variables: c_int,
426        tcm: *mut f64,
427        map: *mut c_int,
428        row: c_int,
429        column: c_int,
430        fields: *const f64,
431    );
432    /// Sets `fields` of row-major block tile of `tcm` created with [`oefpil_tcm_full_new`].
433    ///
434    /// Arguments:
435    ///
436    ///   * Number of `samples` per variable.
437    ///   * Number of `variables` or tiles.
438    ///   * Tiled covariance matrix `tcm` created with
439    ///     <code>[oefpil_tcm_full_new](samples, variables, map)</code>.
440    ///   * Tile `map` created with <code>[oefpil_tilemap_diagtiles_new]\(variables\)</code>.
441    ///   * Tile `row` in `0..variables`.
442    ///   * Tile `column` in `0..variables`.
443    ///   * Tile `fields` to copy into row-major block tile at `row` and `column` of `tcm`. Number
444    ///     of `fields` is `samples.pow(2)`. For block tiles on the diagonal, `fields` must be
445    ///     symmetric (not validated) where row-major and column-major order coincide.
446    pub unsafe fn oefpil_tcm_full_set_tile_full(
447        samples: c_int,
448        variables: c_int,
449        tcm: *mut f64,
450        map: *mut c_int,
451        row: c_int,
452        column: c_int,
453        fields: *const f64,
454    );
455
456    /// Fits the initial estimate of the model's parameter to the data sample of its variable.
457    ///
458    /// Arguments:
459    ///
460    ///   * Function pointer `evaluate` of signature [`Evaluate`] expecting `data` as 1st argument.
461    ///   * `data` passed to `evaluate` as 1st argument, see [`Evaluate`].
462    ///   * Whether the model `is_implicit` (`1`) or explicit (`0`).
463    ///   * Number of `parameters`.
464    ///   * `parameter_mean` vector.
465    ///   * `parameter_covariance` matrix of `parameter_mean` vector.
466    ///   * Number of `samples` per variable.
467    ///   * Number of independent `x_variables`.
468    ///   * `x_sample` of `x_variables` (sample-major).
469    ///   * `y_sample` if not `is_implicit` else [`core::ptr::null`].
470    ///   * `x_mean` of `x_variables` (sample-major).
471    ///   * `y_mean` if not `implicit` else [`core::ptr::null`].
472    ///   * Tiled `covariance` matrix of `x_sample` and `y_sample`.
473    ///   * `covariance_mode` of [`Mode`].
474    ///   * `covariance_map` of [`Mode`] per tile.
475    ///   * Convergence `iteration_limit`.
476    ///   * Convergence `tolerance`.
477    ///   * Log `verbosity`, see [`Verbosity`].
478    ///   * `logfile`, see [`Verbosity`].
479    ///   * `chi_squared` (statistics).
480    ///   * Convergence `criterion`, see [`Criterion`].
481    ///   * Result `info` with `1` for success, `2` for `iteration_limit`, `3` for numerical error.
482    ///   * Number of `iterations` until convergence [`Criterion`] has been reached.
483    ///   * `chi_squared_reduced` (statistics).
484    ///   * Whether `covariance` is `relative` and rescaled by `chi_squared_reduced` or absolute.
485    ///   * `chi_squared_p_value` (statistics).
486    pub unsafe fn oefpil(
487        evaluate: Evaluate,
488        data: *mut c_void,
489        is_implicit: c_int,
490        parameters: c_int,
491        parameter_mean: *mut f64,
492        parameter_covariance: *mut f64,
493        samples: c_int,
494        x_variables: c_int,
495        x_sample: *const f64,
496        y_sample: *const f64,
497        x_mean: *mut f64,
498        y_mean: *mut f64,
499        covariance: *const f64,
500        covariance_mode: c_int,
501        covariance_map: *const c_int,
502        iteration_limit: c_int,
503        tolerance: f64,
504        verbosity: c_int,
505        logfile: *mut FILE,
506        chi_squared: *mut f64,
507        criterion: c_int,
508        info: *mut c_int,
509        iterations: *mut c_int,
510        chi_squared_reduced: *mut f64,
511        relative: bool,
512        chi_squared_p_value: *mut f64,
513    );
514}