Skip to main content

ternary_spreadsheet/
formula.rs

1use crate::cell::{Cell, TernaryValue};
2use crate::grid::Grid;
3
4/// Result of a formula evaluation.
5pub type FormulaResult = Result<f64, FormulaError>;
6
7/// Errors that can occur during formula evaluation.
8#[derive(Debug, Clone, PartialEq)]
9pub enum FormulaError {
10    UnknownFunction(String),
11    InvalidArguments(String),
12    InvalidRange(String),
13    DivisionByZero,
14    NoData,
15}
16
17impl std::fmt::Display for FormulaError {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            FormulaError::UnknownFunction(name) => write!(f, "unknown function: {}", name),
21            FormulaError::InvalidArguments(msg) => write!(f, "invalid arguments: {}", msg),
22            FormulaError::InvalidRange(msg) => write!(f, "invalid range: {}", msg),
23            FormulaError::DivisionByZero => write!(f, "division by zero"),
24            FormulaError::NoData => write!(f, "no data"),
25        }
26    }
27}
28
29/// The formula engine evaluates spreadsheet formulas against a grid.
30pub struct FormulaEngine {
31    grid: Grid,
32}
33
34impl FormulaEngine {
35    /// Create a new engine wrapping a grid.
36    pub fn new(grid: Grid) -> Self {
37        FormulaEngine { grid }
38    }
39
40    /// Get a reference to the underlying grid.
41    pub fn grid(&self) -> &Grid {
42        &self.grid
43    }
44
45    /// Get a mutable reference to the underlying grid.
46    pub fn grid_mut(&mut self) -> &mut Grid {
47        &mut self.grid
48    }
49
50    /// Evaluate a formula string like `=EVOLVE(A1:A10, 100)`.
51    pub fn evaluate(&mut self, formula: &str) -> FormulaResult {
52        let formula = formula.trim();
53        let formula = formula.strip_prefix('=').unwrap_or(formula);
54        
55        // Parse function name and arguments
56        let paren_pos = formula.find('(').ok_or_else(|| {
57            FormulaError::InvalidArguments("expected function(args)".into())
58        })?;
59        let name = formula[..paren_pos].trim().to_uppercase();
60        let inner = formula[paren_pos + 1..].trim();
61        let inner = inner.strip_suffix(')').ok_or_else(|| {
62            FormulaError::InvalidArguments("missing closing parenthesis".into())
63        })?.trim();
64
65        match name.as_str() {
66            "EVOLVE" => self.evolve(inner),
67            "BEST" => self.best(inner),
68            "SPECIES" => self.species(inner),
69            "EXHAUSTIVE" => self.exhaustive(inner),
70            "ENTROPY" => self.entropy(inner),
71            "SUM" => self.sum(inner),
72            "AVG" => self.avg(inner),
73            "COUNT" => self.count(inner),
74            _ => Err(FormulaError::UnknownFunction(name)),
75        }
76    }
77
78    /// Parse a range like "A1:B5" or "A:A" into cell lists.
79    fn parse_range_cells(&self, range_str: &str) -> Result<Vec<Cell>, FormulaError> {
80        let range_str = range_str.trim();
81        
82        if range_str.contains(':') {
83            let parts: Vec<&str> = range_str.splitn(2, ':').collect();
84            let start = parts[0].trim();
85            let end = parts[1].trim();
86
87            // Full column range like "A:A"
88            if start.chars().all(|c| c.is_ascii_alphabetic()) &&
89               end.chars().all(|c| c.is_ascii_alphabetic()) {
90                let col_start = Grid::parse_cell_ref(&format!("{}1", start));
91                let col_end = Grid::parse_cell_ref(&format!("{}{}", end, self.grid.rows()));
92                match (col_start, col_end) {
93                    (Some((_, cs)), Some((_, ce))) if cs == ce => {
94                        Ok(self.grid.col(cs).unwrap_or_default())
95                    }
96                    _ => Err(FormulaError::InvalidRange(range_str.into())),
97                }
98            } else {
99                let start_pos = Grid::parse_cell_ref(start)
100                    .ok_or_else(|| FormulaError::InvalidRange(start.into()))?;
101                let end_pos = Grid::parse_cell_ref(end)
102                    .ok_or_else(|| FormulaError::InvalidRange(end.into()))?;
103                Ok(self.grid.range(start_pos.0, start_pos.1, end_pos.0, end_pos.1)
104                    .into_iter().cloned().collect())
105            }
106        } else {
107            // Single cell
108            let pos = Grid::parse_cell_ref(range_str)
109                .ok_or_else(|| FormulaError::InvalidRange(range_str.into()))?;
110            self.grid.get(pos.0, pos.1)
111                .cloned()
112                .map(|c| vec![c])
113                .ok_or_else(|| FormulaError::InvalidRange(range_str.into()))
114        }
115    }
116
117    /// EVOLVE(range, generations) — run evolution on a range.
118    /// Each generation: compute fitness, keep top 50%, mutate rest.
119    /// Returns the best fitness achieved.
120    fn evolve(&mut self, args: &str) -> FormulaResult {
121        let parts: Vec<&str> = args.splitn(2, ',').collect();
122        if parts.len() != 2 {
123            return Err(FormulaError::InvalidArguments("EVOLVE(range, generations)".into()));
124        }
125        let range_str = parts[0].trim();
126        let generations: u32 = parts[1].trim().parse()
127            .map_err(|_| FormulaError::InvalidArguments("generations must be a number".into()))?;
128
129        // First get cell positions from range
130        let positions = self.get_range_positions(range_str)?;
131        if positions.is_empty() {
132            return Err(FormulaError::NoData);
133        }
134
135        let mut best_fitness = f64::NEG_INFINITY;
136
137        for gen in 0..generations {
138            // Compute fitness for cells in range
139            for &(r, c) in &positions {
140                if let Some(cell) = self.grid.get_mut(r, c) {
141                    cell.set_fitness(cell.compute_default_fitness());
142                }
143            }
144
145            // Sort by fitness, keep top half, mutate bottom half
146            let mut with_fitness: Vec<(f64, usize, usize)> = positions.iter()
147                .filter_map(|&(r, c)| {
148                    self.grid.get(r, c).map(|cell| (cell.fitness, r, c))
149                })
150                .collect();
151            with_fitness.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
152
153            let cutoff = (with_fitness.len() + 1) / 2;
154            for (_, r, c) in with_fitness.into_iter().skip(cutoff) {
155                if let Some(cell) = self.grid.get_mut(r, c) {
156                    let new_val = TernaryValue::from_seed(gen * 17 + (r as u32) * 31 + (c as u32) * 7 + 13);
157                    cell.set_value(new_val);
158                }
159            }
160
161            // Track best
162            if let Some(cell) = self.grid.get(positions[0].0, positions[0].1) {
163                best_fitness = best_fitness.max(cell.fitness);
164            }
165        }
166
167        // Final fitness check
168        for &(r, c) in &positions {
169            if let Some(cell) = self.grid.get(r, c) {
170                best_fitness = best_fitness.max(cell.fitness);
171            }
172        }
173
174        Ok(best_fitness)
175    }
176
177    /// BEST(range) — return the highest fitness in a range.
178    fn best(&self, args: &str) -> FormulaResult {
179        let cells = self.parse_range_cells(args.trim())?;
180        if cells.is_empty() {
181            return Err(FormulaError::NoData);
182        }
183        cells.iter()
184            .map(|c| c.fitness)
185            .fold(None, |acc: Option<f64>, f| {
186                Some(acc.map_or(f, |best| best.max(f)))
187            })
188            .ok_or(FormulaError::NoData)
189    }
190
191    /// SPECIES(range) — count unique species (clusters of same-sign values).
192    fn species(&self, args: &str) -> FormulaResult {
193        let cells = self.parse_range_cells(args.trim())?;
194        if cells.is_empty() {
195            return Err(FormulaError::NoData);
196        }
197        
198        let mut species_count = 0usize;
199        let mut prev_sign: Option<i8> = None;
200        
201        for cell in &cells {
202            let sign = cell.value.as_i8().signum();
203            match prev_sign {
204                Some(p) if p == sign => {} // same species
205                _ => {
206                    if sign != 0 {
207                        species_count += 1;
208                    }
209                }
210            }
211            if sign != 0 {
212                prev_sign = Some(sign);
213            }
214        }
215        
216        Ok(species_count as f64)
217    }
218
219    /// EXHAUSTIVE(range) — try all possible value combinations and return best total fitness.
220    /// For performance, limited to ranges of ≤ 10 cells.
221    fn exhaustive(&mut self, args: &str) -> FormulaResult {
222        let positions = self.get_range_positions(args.trim())?;
223        if positions.is_empty() {
224            return Err(FormulaError::NoData);
225        }
226        if positions.len() > 10 {
227            return Err(FormulaError::InvalidArguments(
228                "EXHAUSTIVE limited to 10 cells (3^10 = 59049 combinations)".into()
229            ));
230        }
231
232        let n = positions.len();
233        let combos = 3usize.pow(n as u32);
234        let mut best_total = f64::NEG_INFINITY;
235
236        // Save original values
237        let original: Vec<TernaryValue> = positions.iter()
238            .map(|&(r, c)| self.grid.get(r, c).map(|c| c.value).unwrap_or(TernaryValue::Neutral))
239            .collect();
240
241        for combo in 0..combos {
242            let mut val = combo;
243            for &(r, c) in &positions {
244                let tv = TernaryValue::from_seed(val as u32);
245                if let Some(cell) = self.grid.get_mut(r, c) {
246                    cell.set_value(tv);
247                }
248                val /= 3;
249            }
250
251            // Compute total fitness
252            let total: f64 = positions.iter()
253                .filter_map(|&(r, c)| {
254                    let cell = self.grid.get_mut(r, c)?;
255                    cell.set_fitness(cell.compute_default_fitness());
256                    Some(cell.fitness)
257                })
258                .sum();
259
260            best_total = best_total.max(total);
261        }
262
263        // Restore original values
264        for (i, &(r, c)) in positions.iter().enumerate() {
265            if let Some(cell) = self.grid.get_mut(r, c) {
266                cell.set_value(original[i]);
267            }
268        }
269
270        Ok(best_total)
271    }
272
273    /// ENTROPY(range) — compute Shannon entropy of ternary values.
274    fn entropy(&self, args: &str) -> FormulaResult {
275        let cells = self.parse_range_cells(args.trim())?;
276        if cells.is_empty() {
277            return Err(FormulaError::NoData);
278        }
279
280        let n = cells.len() as f64;
281        let mut counts = [0usize; 3]; // neg, neutral, pos
282
283        for cell in &cells {
284            match cell.value {
285                TernaryValue::Negative => counts[0] += 1,
286                TernaryValue::Neutral => counts[1] += 1,
287                TernaryValue::Positive => counts[2] += 1,
288            }
289        }
290
291        let mut entropy = 0.0;
292        for &count in &counts {
293            if count > 0 {
294                let p = count as f64 / n;
295                entropy -= p * p.log2();
296            }
297        }
298
299        Ok(entropy)
300    }
301
302    /// SUM(range) — sum all cell values.
303    fn sum(&self, args: &str) -> FormulaResult {
304        let cells = self.parse_range_cells(args.trim())?;
305        if cells.is_empty() {
306            return Err(FormulaError::NoData);
307        }
308        Ok(cells.iter().map(|c| c.value.as_i8() as f64).sum())
309    }
310
311    /// AVG(range) — average of all cell values.
312    fn avg(&self, args: &str) -> FormulaResult {
313        let cells = self.parse_range_cells(args.trim())?;
314        if cells.is_empty() {
315            return Err(FormulaError::NoData);
316        }
317        let sum: f64 = cells.iter().map(|c| c.value.as_i8() as f64).sum();
318        Ok(sum / cells.len() as f64)
319    }
320
321    /// COUNT(range) — count of cells.
322    fn count(&self, args: &str) -> FormulaResult {
323        let cells = self.parse_range_cells(args.trim())?;
324        Ok(cells.len() as f64)
325    }
326
327    /// Helper: get (row, col) positions from a range string.
328    fn get_range_positions(&self, range_str: &str) -> Result<Vec<(usize, usize)>, FormulaError> {
329        let range_str = range_str.trim();
330        
331        if range_str.contains(':') {
332            let parts: Vec<&str> = range_str.splitn(2, ':').collect();
333            let start = parts[0].trim();
334            let end = parts[1].trim();
335
336            if start.chars().all(|c| c.is_ascii_alphabetic()) &&
337               end.chars().all(|c| c.is_ascii_alphabetic()) {
338                let col_s = Grid::parse_cell_ref(&format!("{}1", start));
339                let col_e = Grid::parse_cell_ref(&format!("{}{}", end, self.grid.rows()));
340                match (col_s, col_e) {
341                    (Some((_, cs)), Some((_, ce))) if cs == ce => {
342                        Ok((0..self.grid.rows()).map(|r| (r, cs)).collect())
343                    }
344                    _ => Err(FormulaError::InvalidRange(range_str.into())),
345                }
346            } else {
347                let start_pos = Grid::parse_cell_ref(start)
348                    .ok_or_else(|| FormulaError::InvalidRange(start.into()))?;
349                let end_pos = Grid::parse_cell_ref(end)
350                    .ok_or_else(|| FormulaError::InvalidRange(end.into()))?;
351                let (r1, r2) = (start_pos.0.min(end_pos.0), start_pos.0.max(end_pos.0));
352                let (c1, c2) = (start_pos.1.min(end_pos.1), start_pos.1.max(end_pos.1));
353                let mut positions = Vec::new();
354                for r in r1..=r2 {
355                    for c in c1..=c2 {
356                        if r < self.grid.rows() && c < self.grid.cols() {
357                            positions.push((r, c));
358                        }
359                    }
360                }
361                Ok(positions)
362            }
363        } else {
364            let pos = Grid::parse_cell_ref(range_str)
365                .ok_or_else(|| FormulaError::InvalidRange(range_str.into()))?;
366            if pos.0 < self.grid.rows() && pos.1 < self.grid.cols() {
367                Ok(vec![pos])
368            } else {
369                Err(FormulaError::InvalidRange(range_str.into()))
370            }
371        }
372    }
373}