From cefe2afa8817e181a2b3ce660b1693c800d1a37f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Huvar?= <492849@mail.muni.cz> Date: Sun, 8 Sep 2024 14:35:25 +0200 Subject: [PATCH] Add basic evaluation for BmaUpdateFn, refactor parts of code. --- Cargo.toml | 1 + src/_impl_bma_model.rs | 28 ++++- src/bin/load_json.rs | 17 +-- src/bin/load_xml.rs | 17 +-- src/update_fn/_impl_from_update_fn.rs | 16 +-- src/update_fn/_impl_to_update_fn.rs | 145 +++++++++++++++++++++++++- src/update_fn/bma_fn_tree.rs | 14 +-- src/update_fn/enums.rs | 28 ++--- src/update_fn/parser.rs | 26 ++--- src/update_fn/tokenizer.rs | 42 ++++---- 10 files changed, 238 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f1733fc..c78221b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ serde_json = "1.0" serde_with = "3.9.0" serde-xml-rs = "0.6.0" num-rational = "0.4.2" +num-traits = "0.2.19" diff --git a/src/_impl_bma_model.rs b/src/_impl_bma_model.rs index 32e0683..f25cbc7 100644 --- a/src/_impl_bma_model.rs +++ b/src/_impl_bma_model.rs @@ -6,6 +6,8 @@ use crate::update_fn::bma_fn_tree::BmaFnUpdate; use crate::update_fn::parser::parse_bma_formula; use crate::xml_model::XmlBmaModel; use biodivine_lib_param_bn::{BooleanNetwork, RegulatoryGraph}; +use regex::Regex; +use std::cmp::max; use std::collections::HashMap; impl<'de> JsonSerDe<'de> for BmaModel { @@ -227,11 +229,17 @@ impl From for BmaModel { } impl BmaModel { + fn canonical_var_name(var: &Variable) -> String { + // Regex that matches non-alphanumeric and non-underscore characters + let re = Regex::new(r"[^0-9a-zA-Z_]").unwrap(); + let sanitized_name = re.replace_all(&var.name, ""); + format!("v_{}_{}", var.id, sanitized_name) + } + pub fn to_regulatory_graph(&self) -> Result { let mut variables_map: HashMap = HashMap::new(); for var in &self.model.variables { - let inserted = - variables_map.insert(var.id, format!("v_{}_{}", var.id, var.name.clone())); + let inserted = variables_map.insert(var.id, BmaModel::canonical_var_name(var)); if inserted.is_some() { return Err(format!("Variable ID {} is not unique.", var.id)); } @@ -240,6 +248,7 @@ impl BmaModel { let mut graph = RegulatoryGraph::new(variables); // add regulations + // TODO: decide how to handle "doubled" regulations (of the same vs of different type) self.model .relationships .iter() @@ -260,6 +269,11 @@ impl BmaModel { } pub fn to_boolean_network(&self) -> Result { + // TODO: for now, we are only allowing conversion of Boolean models (not multi-valued) + if self.get_max_var_level() > 1 { + return Err("Cannot convert multi-valued model to a Boolean network.".to_string()); + } + let graph = self.to_regulatory_graph()?; let bn = BooleanNetwork::new(graph); @@ -363,6 +377,16 @@ impl BmaModel { metadata: HashMap::new(), }) } + + pub fn get_max_var_level(&self) -> u32 { + let mut max_level = 0; + self.model.variables.iter().for_each(|v| { + // just in case, lets check both `range_from` and `range_to` + max_level = max(max_level, v.range_from); + max_level = max(max_level, v.range_to); + }); + max_level + } } #[cfg(test)] diff --git a/src/bin/load_json.rs b/src/bin/load_json.rs index 75a09b0..8bfa6e3 100644 --- a/src/bin/load_json.rs +++ b/src/bin/load_json.rs @@ -18,21 +18,8 @@ fn test_parse_all_models_in_dir(models_dir: &str) { let result_model = BmaModel::from_json_str(&json_data); match result_model { - Ok(bma_model) => { - let result_bn = bma_model.to_boolean_network(); - match result_bn { - Ok(_) => { - println!("Successfully parsed and converted model: `{model_path_str}`."); - } - Err(e) => { - println!( - "Failed to convert model `{}` to BN: {:?}.", - model_path_str, e - ); - } - } - - println!("Successfully parsed and converted model: `{model_path_str}`."); + Ok(_) => { + println!("Successfully parsed model `{model_path_str}`."); } Err(e) => { println!("Failed to parse JSON file `{}`: {:?}.", model_path_str, e); diff --git a/src/bin/load_xml.rs b/src/bin/load_xml.rs index 809bda8..14761e3 100644 --- a/src/bin/load_xml.rs +++ b/src/bin/load_xml.rs @@ -18,21 +18,8 @@ fn test_parse_all_models_in_dir(models_dir: &str) { let result_model = BmaModel::from_xml_str(&xml_data); match result_model { - Ok(bma_model) => { - let result_bn = bma_model.to_boolean_network(); - match result_bn { - Ok(_) => { - println!("Successfully parsed and converted model: `{model_path_str}`."); - } - Err(e) => { - println!( - "Failed to convert model `{}` to BN: {:?}.", - model_path_str, e - ); - } - } - - println!("Successfully parsed and converted model: `{model_path_str}`."); + Ok(_) => { + println!("Successfully parsed model `{model_path_str}`."); } Err(e) => { println!("Failed to parse JSON file `{}`: {:?}.", model_path_str, e); diff --git a/src/update_fn/_impl_from_update_fn.rs b/src/update_fn/_impl_from_update_fn.rs index 1aaf7ad..29fb523 100644 --- a/src/update_fn/_impl_from_update_fn.rs +++ b/src/update_fn/_impl_from_update_fn.rs @@ -32,17 +32,17 @@ impl BmaFnUpdate { match op { // AND: map A && B to A * B BinaryOp::And => { - BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times) + BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Mult) } // OR: map A || B to A + B - A * B BinaryOp::Or => { let sum_expr = BmaFnUpdate::mk_arithmetic( left_expr.clone(), right_expr.clone(), - ArithOp::Add, + ArithOp::Plus, ); let prod_expr = - BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times); + BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Mult); BmaFnUpdate::mk_arithmetic(sum_expr, prod_expr, ArithOp::Minus) } // XOR: map A ^ B to A + B - 2 * (A * B) @@ -50,14 +50,14 @@ impl BmaFnUpdate { let sum_expr = BmaFnUpdate::mk_arithmetic( left_expr.clone(), right_expr.clone(), - ArithOp::Add, + ArithOp::Plus, ); let prod_expr = - BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times); + BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Mult); let two_prod_expr = BmaFnUpdate::mk_arithmetic( BmaFnUpdate::mk_constant(2), prod_expr, - ArithOp::Times, + ArithOp::Mult, ); BmaFnUpdate::mk_arithmetic(sum_expr, two_prod_expr, ArithOp::Minus) } @@ -79,8 +79,8 @@ impl BmaFnUpdate { ArithOp::Minus, ); let prod_expr = - BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times); - BmaFnUpdate::mk_arithmetic(not_left_expr, prod_expr, ArithOp::Add) + BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Mult); + BmaFnUpdate::mk_arithmetic(not_left_expr, prod_expr, ArithOp::Plus) } } } diff --git a/src/update_fn/_impl_to_update_fn.rs b/src/update_fn/_impl_to_update_fn.rs index 671be32..da31641 100644 --- a/src/update_fn/_impl_to_update_fn.rs +++ b/src/update_fn/_impl_to_update_fn.rs @@ -1,9 +1,152 @@ -use crate::update_fn::bma_fn_tree::BmaFnUpdate; +use crate::update_fn::bma_fn_tree::{BmaFnUpdate, Expression}; +use crate::update_fn::enums::{AggregateFn, ArithOp, Literal, UnaryFn}; use biodivine_lib_param_bn::FnUpdate; +use num_rational::Rational32; +use num_traits::sign::Signed; +use std::collections::HashMap; impl BmaFnUpdate { pub fn to_update_fn(&self) -> FnUpdate { // TODO: implementation via explicit construction of the function table todo!() } + + pub fn evaluate_in_valuation( + &self, + valuation: &HashMap, + ) -> Result { + match &self.expression_tree { + Expression::Terminal(Literal::Str(name)) => { + if let Some(value) = valuation.get(name) { + Ok(*value) + } else { + Err(format!("Variable `{name}` not found in the valuation.")) + } + } + Expression::Terminal(Literal::Int(value)) => Ok(Rational32::new(*value, 1)), + Expression::Arithmetic(operator, left, right) => { + let left_value = left.evaluate_in_valuation(valuation)?; + let right_value = right.evaluate_in_valuation(valuation)?; + let res = match operator { + ArithOp::Plus => left_value + right_value, + ArithOp::Minus => left_value - right_value, + ArithOp::Mult => left_value * right_value, + ArithOp::Div => left_value / right_value, + }; + Ok(res) + } + Expression::Unary(function, child_node) => { + let child_value = child_node.evaluate_in_valuation(valuation)?; + let res = match function { + UnaryFn::Abs => Rational32::abs(&child_value), + UnaryFn::Ceil => Rational32::ceil(&child_value), + UnaryFn::Floor => Rational32::floor(&child_value), + }; + Ok(res) + } + Expression::Aggregation(function, arguments) => { + let args_values: Vec = arguments + .iter() + .map(|arg| arg.evaluate_in_valuation(valuation)) + .collect::, String>>()?; + let res = match function { + AggregateFn::Avg => { + let count = args_values.len() as i32; + let sum: Rational32 = args_values.iter().cloned().sum(); + sum / Rational32::from_integer(count) + } + AggregateFn::Max => args_values + .iter() + .cloned() + .max() + .expect("List of numbers is empty"), + AggregateFn::Min => args_values + .iter() + .cloned() + .min() + .expect("List of numbers is empty"), + }; + Ok(res) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::update_fn::parser::parse_bma_formula; + use num_rational::Rational32; + use std::collections::HashMap; + + #[test] + fn test_evaluate_terminal_str() { + let expression = parse_bma_formula("x").unwrap(); + let valuation = HashMap::from([("x".to_string(), Rational32::new(5, 1))]); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(5, 1)); + } + + #[test] + fn test_evaluate_terminal_int() { + let expression = parse_bma_formula("7").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(7, 1)); + } + + #[test] + fn test_evaluate_arithmetic_plus() { + let expression = parse_bma_formula("2 + 3").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(5, 1)); + } + + #[test] + fn test_evaluate_arithmetic_mult() { + let expression = parse_bma_formula("4 * x").unwrap(); + let valuation = HashMap::from([("x".to_string(), Rational32::new(2, 1))]); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(8, 1)); + } + + #[test] + fn test_evaluate_unary_abs() { + let expression = parse_bma_formula("abs(5 - 10)").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(5, 1)); + } + + #[test] + fn test_evaluate_aggregation_avg() { + let expression = parse_bma_formula("avg(1, 2, 3)").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(2, 1)); + } + + #[test] + fn test_evaluate_aggregation_max() { + let expression = parse_bma_formula("max(1, 4, 3)").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(4, 1)); + } + + #[test] + fn test_evaluate_aggregation_min() { + let expression = parse_bma_formula("min(1, 2 - 4, 3)").unwrap(); + let valuation = HashMap::new(); + let result = expression.evaluate_in_valuation(&valuation); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Rational32::new(-2, 1)); + } } diff --git a/src/update_fn/bma_fn_tree.rs b/src/update_fn/bma_fn_tree.rs index dc9da52..38c2cef 100644 --- a/src/update_fn/bma_fn_tree.rs +++ b/src/update_fn/bma_fn_tree.rs @@ -1,4 +1,4 @@ -use crate::update_fn::enums::{AggregateOp, ArithOp, Literal, UnaryOp}; +use crate::update_fn::enums::{AggregateFn, ArithOp, Literal, UnaryFn}; use crate::update_fn::parser::parse_bma_fn_tokens; use crate::update_fn::tokenizer::BmaFnToken; use serde::{Deserialize, Serialize}; @@ -9,15 +9,15 @@ use std::fmt; /// /// In particular, a node type can be: /// - A "terminal" node containing a literal (variable, constant). -/// - A "unary" node with a `UnaryOp` and a sub-expression. +/// - A "unary" node with a `UnaryFn` and a sub-expression. /// - A binary "arithmetic" node, with a `BinaryOp` and two sub-expressions. -/// - An "aggregation" node with a `AggregateOp` op and a list of sub-expressions. +/// - An "aggregation" node with a `AggregateFn` op and a list of sub-expressions. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum Expression { Terminal(Literal), - Unary(UnaryOp, Box), + Unary(UnaryFn, Box), Arithmetic(ArithOp, Box, Box), - Aggregation(AggregateOp, Vec>), + Aggregation(AggregateFn, Vec>), } /// A single node in a syntax tree of a FOL formula. @@ -42,7 +42,7 @@ impl BmaFnUpdate { /// Create a "unary" [BmaFnUpdate] from the given arguments. /// /// See also [Expression::Unary]. - pub fn mk_unary(child: BmaFnUpdate, op: UnaryOp) -> BmaFnUpdate { + pub fn mk_unary(child: BmaFnUpdate, op: UnaryFn) -> BmaFnUpdate { let subform_str = format!("{op}({child})"); BmaFnUpdate { function_str: subform_str, @@ -86,7 +86,7 @@ impl BmaFnUpdate { } /// Create a [BmaFnUpdate] representing an aggregation operator applied to given arguments. - pub fn mk_aggregation(op: AggregateOp, inner_nodes: Vec) -> BmaFnUpdate { + pub fn mk_aggregation(op: AggregateFn, inner_nodes: Vec) -> BmaFnUpdate { let max_height = inner_nodes .iter() .map(|node| node.height) diff --git a/src/update_fn/enums.rs b/src/update_fn/enums.rs index 849bcb5..6ac8495 100644 --- a/src/update_fn/enums.rs +++ b/src/update_fn/enums.rs @@ -18,53 +18,53 @@ impl fmt::Display for Literal { #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum ArithOp { - Add, + Plus, Minus, - Times, + Mult, Div, } impl fmt::Display for ArithOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ArithOp::Add => write!(f, "+"), + ArithOp::Plus => write!(f, "+"), ArithOp::Minus => write!(f, "-"), - ArithOp::Times => write!(f, "*"), + ArithOp::Mult => write!(f, "*"), ArithOp::Div => write!(f, "/"), } } } #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum UnaryOp { +pub enum UnaryFn { Ceil, Floor, Abs, } -impl fmt::Display for UnaryOp { +impl fmt::Display for UnaryFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - UnaryOp::Ceil => write!(f, "ceil"), - UnaryOp::Floor => write!(f, "floor"), - UnaryOp::Abs => write!(f, "abs"), + UnaryFn::Ceil => write!(f, "ceil"), + UnaryFn::Floor => write!(f, "floor"), + UnaryFn::Abs => write!(f, "abs"), } } } #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum AggregateOp { +pub enum AggregateFn { Min, Max, Avg, } -impl fmt::Display for AggregateOp { +impl fmt::Display for AggregateFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - AggregateOp::Min => write!(f, "min"), - AggregateOp::Max => write!(f, "max"), - AggregateOp::Avg => write!(f, "avg"), + AggregateFn::Min => write!(f, "min"), + AggregateFn::Max => write!(f, "max"), + AggregateFn::Avg => write!(f, "avg"), } } } diff --git a/src/update_fn/parser.rs b/src/update_fn/parser.rs index 3c3969d..d7d041f 100644 --- a/src/update_fn/parser.rs +++ b/src/update_fn/parser.rs @@ -38,12 +38,12 @@ fn parse_1_div(tokens: &[BmaFnToken]) -> Result { /// Recursive parsing step 2: extract `*` operators. fn parse_2_mul(tokens: &[BmaFnToken]) -> Result { - let mul_token = index_of_first(tokens, BmaFnToken::Binary(ArithOp::Times)); + let mul_token = index_of_first(tokens, BmaFnToken::Binary(ArithOp::Mult)); Ok(if let Some(i) = mul_token { BmaFnUpdate::mk_arithmetic( parse_3_minus(&tokens[..i])?, parse_2_mul(&tokens[(i + 1)..])?, - ArithOp::Times, + ArithOp::Mult, ) } else { parse_3_minus(tokens)? @@ -66,12 +66,12 @@ fn parse_3_minus(tokens: &[BmaFnToken]) -> Result { /// Recursive parsing step 4: extract `+` operators. fn parse_4_plus(tokens: &[BmaFnToken]) -> Result { - let minus_token = index_of_first(tokens, BmaFnToken::Binary(ArithOp::Add)); + let minus_token = index_of_first(tokens, BmaFnToken::Binary(ArithOp::Plus)); Ok(if let Some(i) = minus_token { BmaFnUpdate::mk_arithmetic( parse_5_others(&tokens[..i])?, parse_4_plus(&tokens[(i + 1)..])?, - ArithOp::Add, + ArithOp::Plus, ) } else { parse_5_others(tokens)? @@ -139,7 +139,7 @@ fn parse_5_others(tokens: &[BmaFnToken]) -> Result { mod tests { use super::*; use crate::update_fn::bma_fn_tree::BmaFnUpdate; - use crate::update_fn::enums::{AggregateOp, ArithOp, UnaryOp}; + use crate::update_fn::enums::{AggregateFn, ArithOp, UnaryFn}; #[test] fn test_parse_simple_addition() { @@ -148,7 +148,7 @@ mod tests { let expected = BmaFnUpdate::mk_arithmetic( BmaFnUpdate::mk_constant(3), BmaFnUpdate::mk_constant(5), - ArithOp::Add, + ArithOp::Plus, ); assert_eq!(result, Ok(expected)); } @@ -173,7 +173,7 @@ mod tests { BmaFnUpdate::mk_arithmetic( BmaFnUpdate::mk_constant(8), BmaFnUpdate::mk_constant(4), - ArithOp::Times, + ArithOp::Mult, ), BmaFnUpdate::mk_constant(2), ArithOp::Div, @@ -190,9 +190,9 @@ mod tests { BmaFnUpdate::mk_arithmetic( BmaFnUpdate::mk_constant(5), BmaFnUpdate::mk_constant(2), - ArithOp::Times, + ArithOp::Mult, ), - ArithOp::Add, + ArithOp::Plus, ); assert_eq!(result, Ok(expected)); } @@ -201,7 +201,7 @@ mod tests { fn test_parse_abs_function() { let input = "abs(5)"; let result = parse_bma_formula(input); - let expected = BmaFnUpdate::mk_unary(BmaFnUpdate::mk_constant(5), UnaryOp::Abs); + let expected = BmaFnUpdate::mk_unary(BmaFnUpdate::mk_constant(5), UnaryFn::Abs); assert_eq!(result, Ok(expected)); } @@ -210,14 +210,14 @@ mod tests { let input = "min(3, 5, 5 + variable)"; let result = parse_bma_formula(input); let expected = BmaFnUpdate::mk_aggregation( - AggregateOp::Min, + AggregateFn::Min, vec![ BmaFnUpdate::mk_constant(3), BmaFnUpdate::mk_constant(5), BmaFnUpdate::mk_arithmetic( BmaFnUpdate::mk_constant(5), BmaFnUpdate::mk_variable("variable"), - ArithOp::Add, + ArithOp::Plus, ), ], ); @@ -248,7 +248,7 @@ mod tests { let input = "max(3, 5, 10)"; let result = parse_bma_formula(input); let expected = BmaFnUpdate::mk_aggregation( - AggregateOp::Max, + AggregateFn::Max, vec![ BmaFnUpdate::mk_constant(3), BmaFnUpdate::mk_constant(5), diff --git a/src/update_fn/tokenizer.rs b/src/update_fn/tokenizer.rs index c084a90..71e1c01 100644 --- a/src/update_fn/tokenizer.rs +++ b/src/update_fn/tokenizer.rs @@ -1,4 +1,4 @@ -use crate::update_fn::enums::{AggregateOp, ArithOp, Literal, UnaryOp}; +use crate::update_fn::enums::{AggregateFn, ArithOp, Literal, UnaryFn}; use std::iter::Peekable; use std::str::Chars; @@ -6,9 +6,9 @@ use std::str::Chars; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum BmaFnToken { Atomic(Literal), - Unary(UnaryOp, Box), + Unary(UnaryFn, Box), Binary(ArithOp), - Aggregate(AggregateOp, Vec), + Aggregate(AggregateFn, Vec), TokenList(Vec), } @@ -40,9 +40,9 @@ fn try_tokenize_recursive( while let Some(c) = input_chars.next() { match c { c if c.is_whitespace() => {} - '+' => output.push(BmaFnToken::Binary(ArithOp::Add)), + '+' => output.push(BmaFnToken::Binary(ArithOp::Plus)), '-' => output.push(BmaFnToken::Binary(ArithOp::Minus)), - '*' => output.push(BmaFnToken::Binary(ArithOp::Times)), + '*' => output.push(BmaFnToken::Binary(ArithOp::Mult)), '/' => output.push(BmaFnToken::Binary(ArithOp::Div)), '(' => { // start a nested token group @@ -67,35 +67,35 @@ fn try_tokenize_recursive( "abs" => { let args = collect_fn_arguments(input_chars)?; output.push(BmaFnToken::Unary( - UnaryOp::Abs, + UnaryFn::Abs, Box::new(args[0].to_owned()), )) } "ceil" => { let args = collect_fn_arguments(input_chars)?; output.push(BmaFnToken::Unary( - UnaryOp::Ceil, + UnaryFn::Ceil, Box::new(args[0].to_owned()), )) } "floor" => { let args = collect_fn_arguments(input_chars)?; output.push(BmaFnToken::Unary( - UnaryOp::Floor, + UnaryFn::Floor, Box::new(args[0].to_owned()), )) } "min" => { let args = collect_fn_arguments(input_chars)?; - output.push(BmaFnToken::Aggregate(AggregateOp::Min, args)); + output.push(BmaFnToken::Aggregate(AggregateFn::Min, args)); } "max" => { let args = collect_fn_arguments(input_chars)?; - output.push(BmaFnToken::Aggregate(AggregateOp::Max, args)); + output.push(BmaFnToken::Aggregate(AggregateFn::Max, args)); } "avg" => { let args = collect_fn_arguments(input_chars)?; - output.push(BmaFnToken::Aggregate(AggregateOp::Avg, args)); + output.push(BmaFnToken::Aggregate(AggregateFn::Avg, args)); } _ => { // Assume it’s a literal @@ -198,7 +198,7 @@ fn collect_fn_arguments(input_chars: &mut Peekable) -> Result