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    fn fit_transform(y: &[f64]) -> Result<(Self::State, Vec<f64>), TransformError> {
69        let state = Self::fit(y)?;
70        let transformed = Self::transform_slice(&state, y)?;
71        Ok((state, transformed))
72    }
73
74    /// Преобразует срез target в новый `Vec`.
75    ///
76    /// # Errors
77    ///
78    /// Возвращает [`TransformError::NonFiniteValue`], если вход содержит
79    /// `NaN` или infinity. Конкретные transform-ы могут усиливать проверку
80    /// domain-а, например требовать строго положительные значения.
81    fn transform_slice(state: &Self::State, y: &[f64]) -> Result<Vec<f64>, TransformError> {
82        let mut out = vec![0.0; y.len()];
83        Self::transform_into(state, y, &mut out)?;
84        Ok(out)
85    }
86
87    /// Преобразует срез target в caller-provided output buffer.
88    ///
89    /// # Errors
90    ///
91    /// Возвращает [`TransformError::LengthMismatch`], если длина `out` не
92    /// совпадает с длиной входа. Возвращает [`TransformError::NonFiniteValue`],
93    /// если вход содержит `NaN` или infinity. Конкретные transform-ы могут
94    /// усиливать проверку domain-а.
95    fn transform_into(
96        state: &Self::State,
97        y: &[f64],
98        out: &mut [f64],
99    ) -> Result<(), TransformError> {
100        map_slice_into(y, out, validate_finite, |value| {
101            Self::transform(state, value)
102        })
103    }
104
105    /// Возвращает срез значений на исходную шкалу в новый `Vec`.
106    ///
107    /// # Errors
108    ///
109    /// Возвращает [`TransformError::NonFiniteValue`], если значения на
110    /// transform-шкале содержат `NaN` или infinity.
111    fn inverse_slice(state: &Self::State, values: &[f64]) -> Result<Vec<f64>, TransformError> {
112        let mut out = vec![0.0; values.len()];
113        Self::inverse_into(state, values, &mut out)?;
114        Ok(out)
115    }
116
117    /// Возвращает transform-scale values на исходную шкалу в caller-provided buffer.
118    ///
119    /// # Errors
120    ///
121    /// Возвращает [`TransformError::LengthMismatch`], если длина `out` не
122    /// совпадает с длиной входа. Возвращает [`TransformError::NonFiniteValue`],
123    /// если вход содержит `NaN` или infinity.
124    fn inverse_into(
125        state: &Self::State,
126        values: &[f64],
127        out: &mut [f64],
128    ) -> Result<(), TransformError> {
129        map_slice_into(values, out, validate_finite, |value| {
130            Self::inverse(state, value)
131        })
132    }
133}
134
135pub(crate) fn map_slice_into(
136    values: &[f64],
137    out: &mut [f64],
138    validate: impl FnOnce(&[f64]) -> Result<(), TransformError>,
139    mut map: impl FnMut(f64) -> f64,
140) -> Result<(), TransformError> {
141    validate_output_len(values.len(), out.len())?;
142    validate(values)?;
143    for (out, value) in out.iter_mut().zip(values.iter().copied()) {
144        *out = map(value);
145    }
146    Ok(())
147}
148
149pub(crate) fn validate_output_len(expected: usize, actual: usize) -> Result<(), TransformError> {
150    if actual == expected {
151        Ok(())
152    } else {
153        Err(TransformError::LengthMismatch { expected, actual })
154    }
155}
156
157pub(crate) fn validate_non_empty_finite(values: &[f64]) -> Result<(), TransformError> {
158    if values.is_empty() {
159        return Err(TransformError::EmptyInput);
160    }
161    validate_finite(values)
162}
163
164pub(crate) fn validate_finite(values: &[f64]) -> Result<(), TransformError> {
165    if values.iter().all(|value| value.is_finite()) {
166        Ok(())
167    } else {
168        Err(TransformError::NonFiniteValue)
169    }
170}
171
172pub(crate) fn validate_positive(values: &[f64]) -> Result<(), TransformError> {
173    validate_non_empty_finite(values)?;
174    if values.iter().all(|value| *value > 0.0) {
175        Ok(())
176    } else {
177        Err(TransformError::NonPositiveValue)
178    }
179}
180
181pub(crate) fn validate_shifted_non_negative(
182    values: &[f64],
183    shift: f64,
184) -> Result<(), TransformError> {
185    validate_finite(values)?;
186    if values.iter().all(|value| *value + shift >= 0.0) {
187        Ok(())
188    } else {
189        Err(TransformError::BelowLowerBound)
190    }
191}
192
193pub(crate) fn median_sorted(values: &[f64]) -> Option<f64> {
194    match values.len() {
195        0 => None,
196        len if len % 2 == 1 => Some(values[len / 2]),
197        len => {
198            let upper = len / 2;
199            Some((values[upper - 1] + values[upper]) / 2.0)
200        }
201    }
202}
203
204/// Наиболее часто используемые импорты из `gamlss-transform`.
205pub mod prelude {
206    pub use crate::{
207        AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
208        Log1pShiftState, LogState, Standardize, StandardizeState, TargetTransform, TransformError,
209    };
210}