napparent_tabular/
activation.rs1use crate::preprocess::BinDepth;
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct PairStats {
19 pub sum: f32,
20 pub count: f32,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq)]
25pub struct EffectContext {
26 pub global_mean_outcome: f32,
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31pub enum KgPairActivation {
32 #[default]
34 LogFrequencyWeightedMean,
35}
36
37impl KgPairActivation {
38 pub fn activate(&self, stats: PairStats) -> f32 {
39 match self {
40 KgPairActivation::LogFrequencyWeightedMean => log_frequency_weighted_mean(stats),
41 }
42 }
43}
44
45fn log_frequency_weighted_mean(stats: PairStats) -> f32 {
46 if stats.count <= 1.0 {
47 0.0
48 } else {
49 (stats.sum / stats.count) * stats.count.log10()
50 }
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
55pub enum EffectActivation {
56 #[default]
58 GlobalMeanContrast,
59}
60
61impl EffectActivation {
62 pub fn activate(&self, combined: f32, ctx: &EffectContext) -> f32 {
63 match self {
64 EffectActivation::GlobalMeanContrast => combined - ctx.global_mean_outcome,
65 }
66 }
67}
68
69#[derive(Clone, Debug, Default, PartialEq, Eq)]
71pub struct ActivationConfig {
72 pub kg_pair: KgPairActivation,
73 pub effect: EffectActivation,
74}
75
76#[derive(Clone, Debug, Default, PartialEq, Eq)]
78pub struct TransformLimits {
79 pub max_rows: Option<usize>,
80 pub max_active_columns: Option<usize>,
81 pub max_col_pairs: Option<usize>,
82 pub max_vals_map_keys: Option<usize>,
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct TransformConfig {
88 pub bin_depth: BinDepth,
89 pub activation: ActivationConfig,
90 pub verbose: bool,
92 pub limits: TransformLimits,
93}
94
95impl TransformConfig {
96 pub fn new(bin_depth: BinDepth) -> Self {
97 Self {
98 bin_depth,
99 activation: ActivationConfig::default(),
100 verbose: false,
101 limits: TransformLimits::default(),
102 }
103 }
104
105 pub fn with_activation(mut self, activation: ActivationConfig) -> Self {
106 self.activation = activation;
107 self
108 }
109
110 pub fn with_verbose(mut self, verbose: bool) -> Self {
111 self.verbose = verbose;
112 self
113 }
114
115 pub fn with_limits(mut self, limits: TransformLimits) -> Self {
116 self.limits = limits;
117 self
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn log_frequency_weighted_mean_cases() {
127 let act = KgPairActivation::LogFrequencyWeightedMean;
128 assert_eq!(
129 act.activate(PairStats {
130 sum: 10.0,
131 count: 1.0
132 }),
133 0.0
134 );
135 assert_eq!(
136 act.activate(PairStats {
137 sum: 5.0,
138 count: 0.5
139 }),
140 0.0
141 );
142 let v = act.activate(PairStats {
143 sum: 50.0,
144 count: 10.0,
145 });
146 assert!((v - 5.0_f32).abs() < 1e-5);
147 }
148
149 #[test]
150 fn global_mean_contrast() {
151 let act = EffectActivation::GlobalMeanContrast;
152 let ctx = EffectContext {
153 global_mean_outcome: 1.0,
154 };
155 assert!((act.activate(3.0, &ctx) - 2.0).abs() < 1e-6);
156 }
157}