Skip to main content

gamlss_transform/
lib.rs

1#![forbid(unsafe_code)]
2//! Target transforms for GAMLSS modeling.
3
4use thiserror::Error;
5
6pub mod transforms;
7
8pub use transforms::{
9    AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
10    Log1pShiftState, LogState, Standardize, StandardizeState,
11};
12
13/// Ошибки построения и применения target transforms.
14#[derive(Debug, Clone, PartialEq, Eq, Error)]
15pub enum TransformError {
16    /// Target vector пуст.
17    #[error("target vector must contain at least one value")]
18    EmptyInput,
19
20    /// Target содержит `NaN` или infinity.
21    #[error("target contains a non-finite value")]
22    NonFiniteValue,
23
24    /// Transform требует строго положительный target.
25    #[error("target value must be finite and > 0")]
26    NonPositiveValue,
27
28    /// Transform получил значение ниже обученной нижней границы.
29    #[error("target value is below the fitted lower bound")]
30    BelowLowerBound,
31
32    /// Standardize transform получил нулевую дисперсию.
33    #[error("target scale must be positive")]
34    ZeroScale,
35
36    /// Output buffer length does not match the input length.
37    #[error("output length is {actual}, expected {expected}")]
38    LengthMismatch {
39        /// Expected output length.
40        expected: usize,
41        /// Actual output length.
42        actual: usize,
43    },
44}
45
46/// Transform целевой переменной с состоянием, оцениваемым на обучающем target.
47pub trait TargetTransform {
48    /// Состояние transform-а, сохраняемое вместе с обученной моделью.
49    type State;
50
51    /// Оценивает состояние transform-а по обучающему target.
52    ///
53    /// # Errors
54    ///
55    /// Возвращает [`TransformError`], если target пуст, содержит не-finite
56    /// значения или нарушает domain-инварианты конкретного transform-а.
57    fn fit(y: &[f64]) -> Result<Self::State, TransformError>;
58    /// Преобразует одно значение target.
59    fn transform(state: &Self::State, y: f64) -> f64;
60    /// Возвращает значение на исходную шкалу.
61    fn inverse(state: &Self::State, value: f64) -> f64;
62
63    /// Оценивает состояние и преобразует весь target.
64    ///
65    /// # Errors
66    ///
67    /// Возвращает ошибку из [`Self::fit`] или [`Self::transform_slice`].
68    #[inline]
69    fn fit_transform(y: &[f64]) -> Result<(Self::State, Vec<f64>), TransformError> {
70        let state = Self::fit(y)?;
71        let transformed = Self::transform_slice(&state, y)?;
72        Ok((state, transformed))
73    }
74
75    /// Преобразует срез target в новый `Vec`.
76    ///
77    /// # Errors
78    ///
79    /// Возвращает [`TransformError::NonFiniteValue`], если вход содержит
80    /// `NaN` или infinity. Конкретные transform-ы могут усиливать проверку
81    /// domain-а, например требовать строго положительные значения.
82    #[inline]
83    fn transform_slice(state: &Self::State, y: &[f64]) -> Result<Vec<f64>, TransformError> {
84        let mut out = vec![0.0; y.len()];
85        Self::transform_into(state, y, &mut out)?;
86        Ok(out)
87    }
88
89    /// Преобразует срез target в caller-provided output buffer.
90    ///
91    /// # Errors
92    ///
93    /// Возвращает [`TransformError::LengthMismatch`], если длина `out` не
94    /// совпадает с длиной входа. Возвращает [`TransformError::NonFiniteValue`],
95    /// если вход содержит `NaN` или infinity. Конкретные transform-ы могут
96    /// усиливать проверку domain-а.
97    #[inline]
98    fn transform_into(
99        state: &Self::State,
100        y: &[f64],
101        out: &mut [f64],
102    ) -> Result<(), TransformError> {
103        map_slice_into(y, out, validate_finite, |value| {
104            Self::transform(state, value)
105        })
106    }
107
108    /// Возвращает срез значений на исходную шкалу в новый `Vec`.
109    ///
110    /// # Errors
111    ///
112    /// Возвращает [`TransformError::NonFiniteValue`], если значения на
113    /// transform-шкале содержат `NaN` или infinity.
114    #[inline]
115    fn inverse_slice(state: &Self::State, values: &[f64]) -> Result<Vec<f64>, TransformError> {
116        let mut out = vec![0.0; values.len()];
117        Self::inverse_into(state, values, &mut out)?;
118        Ok(out)
119    }
120
121    /// Возвращает transform-scale values на исходную шкалу в caller-provided buffer.
122    ///
123    /// # Errors
124    ///
125    /// Возвращает [`TransformError::LengthMismatch`], если длина `out` не
126    /// совпадает с длиной входа. Возвращает [`TransformError::NonFiniteValue`],
127    /// если вход содержит `NaN` или infinity.
128    #[inline]
129    fn inverse_into(
130        state: &Self::State,
131        values: &[f64],
132        out: &mut [f64],
133    ) -> Result<(), TransformError> {
134        map_slice_into(values, out, validate_finite, |value| {
135            Self::inverse(state, value)
136        })
137    }
138}
139
140pub(crate) fn map_slice_into(
141    values: &[f64],
142    out: &mut [f64],
143    validate: impl FnOnce(&[f64]) -> Result<(), TransformError>,
144    mut map: impl FnMut(f64) -> f64,
145) -> Result<(), TransformError> {
146    validate_output_len(values.len(), out.len())?;
147    validate(values)?;
148    for (out, value) in out.iter_mut().zip(values.iter().copied()) {
149        *out = map(value);
150    }
151    Ok(())
152}
153
154pub(crate) fn validate_output_len(expected: usize, actual: usize) -> Result<(), TransformError> {
155    if actual == expected {
156        Ok(())
157    } else {
158        Err(TransformError::LengthMismatch { expected, actual })
159    }
160}
161
162pub(crate) fn validate_non_empty_finite(values: &[f64]) -> Result<(), TransformError> {
163    if values.is_empty() {
164        return Err(TransformError::EmptyInput);
165    }
166    validate_finite(values)
167}
168
169pub(crate) fn validate_finite(values: &[f64]) -> Result<(), TransformError> {
170    if values.iter().all(|value| value.is_finite()) {
171        Ok(())
172    } else {
173        Err(TransformError::NonFiniteValue)
174    }
175}
176
177pub(crate) fn validate_positive(values: &[f64]) -> Result<(), TransformError> {
178    validate_non_empty_finite(values)?;
179    if values.iter().all(|value| *value > 0.0) {
180        Ok(())
181    } else {
182        Err(TransformError::NonPositiveValue)
183    }
184}
185
186pub(crate) fn validate_shifted_non_negative(
187    values: &[f64],
188    shift: f64,
189) -> Result<(), TransformError> {
190    validate_finite(values)?;
191    if values.iter().all(|value| *value + shift >= 0.0) {
192        Ok(())
193    } else {
194        Err(TransformError::BelowLowerBound)
195    }
196}
197
198pub(crate) fn median_sorted(values: &[f64]) -> Option<f64> {
199    match values.len() {
200        0 => None,
201        len if len % 2 == 1 => Some(values[len / 2]),
202        len => {
203            let upper = len / 2;
204            Some((values[upper - 1] + values[upper]) / 2.0)
205        }
206    }
207}
208
209/// Наиболее часто используемые импорты из `gamlss-transform`.
210pub mod prelude {
211    pub use crate::{
212        AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
213        Log1pShiftState, LogState, Standardize, StandardizeState, TargetTransform, TransformError,
214    };
215}