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}