polars_plan/plans/optimizer/simplify_expr/
mod.rs

1mod simplify_functions;
2
3use polars_utils::floor_divmod::FloorDivMod;
4use polars_utils::total_ord::ToTotalOrd;
5use simplify_functions::optimize_functions;
6
7use crate::plans::*;
8
9fn new_null_count(input: &[ExprIR]) -> AExpr {
10    AExpr::Function {
11        input: input.to_vec(),
12        function: FunctionExpr::NullCount,
13        options: FunctionOptions {
14            collect_groups: ApplyOptions::GroupWise,
15            flags: FunctionFlags::ALLOW_GROUP_AWARE | FunctionFlags::RETURNS_SCALAR,
16            ..Default::default()
17        },
18    }
19}
20
21macro_rules! eval_binary_same_type {
22    ($lhs:expr, $rhs:expr, |$l: ident, $r: ident| $ret: expr) => {{
23        if let (AExpr::Literal(lit_left), AExpr::Literal(lit_right)) = ($lhs, $rhs) {
24            match (lit_left, lit_right) {
25                (LiteralValue::Float32($l), LiteralValue::Float32($r)) => {
26                    Some(AExpr::Literal(LiteralValue::Float32($ret)))
27                },
28                (LiteralValue::Float64($l), LiteralValue::Float64($r)) => {
29                    Some(AExpr::Literal(LiteralValue::Float64($ret)))
30                },
31                #[cfg(feature = "dtype-i8")]
32                (LiteralValue::Int8($l), LiteralValue::Int8($r)) => {
33                    Some(AExpr::Literal(LiteralValue::Int8($ret)))
34                },
35                #[cfg(feature = "dtype-i16")]
36                (LiteralValue::Int16($l), LiteralValue::Int16($r)) => {
37                    Some(AExpr::Literal(LiteralValue::Int16($ret)))
38                },
39                (LiteralValue::Int32($l), LiteralValue::Int32($r)) => {
40                    Some(AExpr::Literal(LiteralValue::Int32($ret)))
41                },
42                (LiteralValue::Int64($l), LiteralValue::Int64($r)) => {
43                    Some(AExpr::Literal(LiteralValue::Int64($ret)))
44                },
45                #[cfg(feature = "dtype-u8")]
46                (LiteralValue::UInt8($l), LiteralValue::UInt8($r)) => {
47                    Some(AExpr::Literal(LiteralValue::UInt8($ret)))
48                },
49                #[cfg(feature = "dtype-u16")]
50                (LiteralValue::UInt16($l), LiteralValue::UInt16($r)) => {
51                    Some(AExpr::Literal(LiteralValue::UInt16($ret)))
52                },
53                (LiteralValue::UInt32($l), LiteralValue::UInt32($r)) => {
54                    Some(AExpr::Literal(LiteralValue::UInt32($ret)))
55                },
56                (LiteralValue::UInt64($l), LiteralValue::UInt64($r)) => {
57                    Some(AExpr::Literal(LiteralValue::UInt64($ret)))
58                },
59                (LiteralValue::Float($l), LiteralValue::Float($r)) => {
60                    Some(AExpr::Literal(LiteralValue::Float($ret)))
61                },
62                (LiteralValue::Int($l), LiteralValue::Int($r)) => {
63                    Some(AExpr::Literal(LiteralValue::Int($ret)))
64                },
65                _ => None,
66            }
67        } else {
68            None
69        }
70    }};
71}
72
73macro_rules! eval_binary_cmp_same_type {
74    ($lhs:expr, $operand: tt, $rhs:expr) => {{
75    if let (AExpr::Literal(lit_left), AExpr::Literal(lit_right)) = ($lhs, $rhs) {
76        match (lit_left, lit_right) {
77            (LiteralValue::Float32(x), LiteralValue::Float32(y)) => {
78                Some(AExpr::Literal(LiteralValue::Boolean(x.to_total_ord() $operand y.to_total_ord())))
79            }
80            (LiteralValue::Float64(x), LiteralValue::Float64(y)) => {
81                Some(AExpr::Literal(LiteralValue::Boolean(x.to_total_ord() $operand y.to_total_ord())))
82            }
83            #[cfg(feature = "dtype-i8")]
84            (LiteralValue::Int8(x), LiteralValue::Int8(y)) => {
85                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
86            }
87            #[cfg(feature = "dtype-i16")]
88            (LiteralValue::Int16(x), LiteralValue::Int16(y)) => {
89                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
90            }
91            (LiteralValue::Int32(x), LiteralValue::Int32(y)) => {
92                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
93            }
94            (LiteralValue::Int64(x), LiteralValue::Int64(y)) => {
95                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
96            }
97            #[cfg(feature = "dtype-u8")]
98            (LiteralValue::UInt8(x), LiteralValue::UInt8(y)) => {
99                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
100            }
101            #[cfg(feature = "dtype-u16")]
102            (LiteralValue::UInt16(x), LiteralValue::UInt16(y)) => {
103                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
104            }
105            (LiteralValue::UInt32(x), LiteralValue::UInt32(y)) => {
106                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
107            }
108            (LiteralValue::UInt64(x), LiteralValue::UInt64(y)) => {
109                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
110            }
111            (LiteralValue::Boolean(x), LiteralValue::Boolean(y)) => {
112                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
113            },
114            (LiteralValue::Int(x), LiteralValue::Int(y)) => {
115                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
116            }
117            (LiteralValue::Float(x), LiteralValue::Float(y)) => {
118                Some(AExpr::Literal(LiteralValue::Boolean(x $operand y)))
119            }
120            _ => None,
121        }
122    } else {
123        None
124    }
125
126    }}
127}
128
129pub struct SimplifyBooleanRule {}
130
131impl OptimizationRule for SimplifyBooleanRule {
132    fn optimize_expr(
133        &mut self,
134        expr_arena: &mut Arena<AExpr>,
135        expr_node: Node,
136        lp_arena: &Arena<IR>,
137        lp_node: Node,
138    ) -> PolarsResult<Option<AExpr>> {
139        let expr = expr_arena.get(expr_node);
140        let in_filter = matches!(lp_arena.get(lp_node), IR::Filter { .. });
141
142        let out = match expr {
143            // true AND x => x
144            AExpr::BinaryExpr {
145                left,
146                op: Operator::And,
147                right,
148            } if matches!(
149                expr_arena.get(*left),
150                AExpr::Literal(LiteralValue::Boolean(true))
151            ) && in_filter =>
152            {
153                // Only in filter as we might change the name from "literal"
154                // to whatever lhs columns is.
155                return Ok(Some(expr_arena.get(*right).clone()));
156            },
157            // x AND true => x
158            AExpr::BinaryExpr {
159                left,
160                op: Operator::And,
161                right,
162            } if matches!(
163                expr_arena.get(*right),
164                AExpr::Literal(LiteralValue::Boolean(true))
165            ) =>
166            {
167                Some(expr_arena.get(*left).clone())
168            },
169
170            // x AND false -> false
171            // FIXME: we need an optimizer redesign to allow x & false to be optimized
172            // in general as we can forget the length of a series otherwise.
173            AExpr::BinaryExpr {
174                left,
175                op: Operator::And,
176                right,
177            } if matches!(expr_arena.get(*left), AExpr::Literal(_))
178                && matches!(
179                    expr_arena.get(*right),
180                    AExpr::Literal(LiteralValue::Boolean(false))
181                ) =>
182            {
183                Some(AExpr::Literal(LiteralValue::Boolean(false)))
184            },
185
186            // false AND x -> false
187            // FIXME: we need an optimizer redesign to allow false & x to be optimized
188            // in general as we can forget the length of a series otherwise.
189            AExpr::BinaryExpr {
190                left,
191                op: Operator::And,
192                right,
193            } if matches!(
194                expr_arena.get(*left),
195                AExpr::Literal(LiteralValue::Boolean(false))
196            ) && matches!(expr_arena.get(*right), AExpr::Literal(_)) =>
197            {
198                Some(AExpr::Literal(LiteralValue::Boolean(false)))
199            },
200
201            // false or x => x
202            AExpr::BinaryExpr {
203                left,
204                op: Operator::Or,
205                right,
206            } if matches!(
207                expr_arena.get(*left),
208                AExpr::Literal(LiteralValue::Boolean(false))
209            ) && in_filter =>
210            {
211                // Only in filter as we might change the name from "literal"
212                // to whatever lhs columns is.
213                return Ok(Some(expr_arena.get(*right).clone()));
214            },
215            // x or false => x
216            AExpr::BinaryExpr {
217                left,
218                op: Operator::Or,
219                right,
220                ..
221            } if matches!(
222                expr_arena.get(*right),
223                AExpr::Literal(LiteralValue::Boolean(false))
224            ) =>
225            {
226                Some(expr_arena.get(*left).clone())
227            },
228
229            // true OR x => true
230            // FIXME: we need an optimizer redesign to allow true | x to be optimized
231            // in general as we can forget the length of a series otherwise.
232            AExpr::BinaryExpr {
233                left,
234                op: Operator::Or,
235                right,
236            } if matches!(expr_arena.get(*left), AExpr::Literal(_))
237                && matches!(
238                    expr_arena.get(*right),
239                    AExpr::Literal(LiteralValue::Boolean(true))
240                ) =>
241            {
242                Some(AExpr::Literal(LiteralValue::Boolean(true)))
243            },
244
245            // x OR true => true
246            // FIXME: we need an optimizer redesign to allow true | x to be optimized
247            // in general as we can forget the length of a series otherwise.
248            AExpr::BinaryExpr {
249                left,
250                op: Operator::Or,
251                right,
252            } if matches!(
253                expr_arena.get(*left),
254                AExpr::Literal(LiteralValue::Boolean(true))
255            ) && matches!(expr_arena.get(*right), AExpr::Literal(_)) =>
256            {
257                Some(AExpr::Literal(LiteralValue::Boolean(true)))
258            },
259            AExpr::Function {
260                input,
261                function: FunctionExpr::Negate,
262                ..
263            } if input.len() == 1 => {
264                let input = &input[0];
265                let ae = expr_arena.get(input.node());
266                eval_negate(ae)
267            },
268            _ => None,
269        };
270        Ok(out)
271    }
272}
273
274fn eval_negate(ae: &AExpr) -> Option<AExpr> {
275    let out = match ae {
276        AExpr::Literal(lv) => match lv {
277            #[cfg(feature = "dtype-i8")]
278            LiteralValue::Int8(v) => LiteralValue::Int8(-*v),
279            #[cfg(feature = "dtype-i16")]
280            LiteralValue::Int16(v) => LiteralValue::Int16(-*v),
281            LiteralValue::Int32(v) => LiteralValue::Int32(-*v),
282            LiteralValue::Int64(v) => LiteralValue::Int64(-*v),
283            LiteralValue::Float32(v) => LiteralValue::Float32(-*v),
284            LiteralValue::Float64(v) => LiteralValue::Float64(-*v),
285            LiteralValue::Float(v) => LiteralValue::Float(-*v),
286            LiteralValue::Int(v) => LiteralValue::Int(-*v),
287            _ => return None,
288        },
289        _ => return None,
290    };
291    Some(AExpr::Literal(out))
292}
293
294fn eval_bitwise<F>(left: &AExpr, right: &AExpr, operation: F) -> Option<AExpr>
295where
296    F: Fn(bool, bool) -> bool,
297{
298    if let (AExpr::Literal(lit_left), AExpr::Literal(lit_right)) = (left, right) {
299        return match (lit_left, lit_right) {
300            (LiteralValue::Boolean(x), LiteralValue::Boolean(y)) => {
301                Some(AExpr::Literal(LiteralValue::Boolean(operation(*x, *y))))
302            },
303            _ => None,
304        };
305    }
306    None
307}
308
309#[cfg(all(feature = "strings", feature = "concat_str"))]
310fn string_addition_to_linear_concat(
311    lp_arena: &Arena<IR>,
312    lp_node: Node,
313    expr_arena: &Arena<AExpr>,
314    left_node: Node,
315    right_node: Node,
316    left_aexpr: &AExpr,
317    right_aexpr: &AExpr,
318) -> Option<AExpr> {
319    {
320        let lp = lp_arena.get(lp_node);
321        let input = lp.get_input()?;
322        let schema = lp_arena.get(input).schema(lp_arena);
323        let left_e = ExprIR::from_node(left_node, expr_arena);
324        let right_e = ExprIR::from_node(right_node, expr_arena);
325
326        let get_type = |ae: &AExpr| ae.get_type(&schema, Context::Default, expr_arena).ok();
327        let type_a = get_type(left_aexpr).or_else(|| get_type(right_aexpr))?;
328        let type_b = get_type(right_aexpr).or_else(|| get_type(right_aexpr))?;
329
330        if type_a != type_b {
331            return None;
332        }
333
334        if type_a.is_string() {
335            match (left_aexpr, right_aexpr) {
336                // concat + concat
337                (
338                    AExpr::Function {
339                        input: input_left,
340                        function:
341                            ref fun_l @ FunctionExpr::StringExpr(StringFunction::ConcatHorizontal {
342                                delimiter: sep_l,
343                                ignore_nulls: ignore_nulls_l,
344                            }),
345                        options,
346                    },
347                    AExpr::Function {
348                        input: input_right,
349                        function:
350                            FunctionExpr::StringExpr(StringFunction::ConcatHorizontal {
351                                delimiter: sep_r,
352                                ignore_nulls: ignore_nulls_r,
353                            }),
354                        ..
355                    },
356                ) => {
357                    if sep_l.is_empty() && sep_r.is_empty() && ignore_nulls_l == ignore_nulls_r {
358                        let mut input = Vec::with_capacity(input_left.len() + input_right.len());
359                        input.extend_from_slice(input_left);
360                        input.extend_from_slice(input_right);
361                        Some(AExpr::Function {
362                            input,
363                            function: fun_l.clone(),
364                            options: *options,
365                        })
366                    } else {
367                        None
368                    }
369                },
370                // concat + str
371                (
372                    AExpr::Function {
373                        input,
374                        function:
375                            ref fun @ FunctionExpr::StringExpr(StringFunction::ConcatHorizontal {
376                                delimiter: sep,
377                                ignore_nulls,
378                            }),
379                        options,
380                    },
381                    _,
382                ) => {
383                    if sep.is_empty() && !ignore_nulls {
384                        let mut input = input.clone();
385                        input.push(right_e);
386                        Some(AExpr::Function {
387                            input,
388                            function: fun.clone(),
389                            options: *options,
390                        })
391                    } else {
392                        None
393                    }
394                },
395                // str + concat
396                (
397                    _,
398                    AExpr::Function {
399                        input: input_right,
400                        function:
401                            ref fun @ FunctionExpr::StringExpr(StringFunction::ConcatHorizontal {
402                                delimiter: sep,
403                                ignore_nulls,
404                            }),
405                        options,
406                    },
407                ) => {
408                    if sep.is_empty() && !ignore_nulls {
409                        let mut input = Vec::with_capacity(1 + input_right.len());
410                        input.push(left_e);
411                        input.extend_from_slice(input_right);
412                        Some(AExpr::Function {
413                            input,
414                            function: fun.clone(),
415                            options: *options,
416                        })
417                    } else {
418                        None
419                    }
420                },
421                _ => Some(AExpr::Function {
422                    input: vec![left_e, right_e],
423                    function: StringFunction::ConcatHorizontal {
424                        delimiter: "".into(),
425                        ignore_nulls: false,
426                    }
427                    .into(),
428                    options: FunctionOptions {
429                        collect_groups: ApplyOptions::ElementWise,
430                        flags: FunctionFlags::default()
431                            | FunctionFlags::INPUT_WILDCARD_EXPANSION
432                                & !FunctionFlags::RETURNS_SCALAR,
433                        ..Default::default()
434                    },
435                }),
436            }
437        } else {
438            None
439        }
440    }
441}
442
443pub struct SimplifyExprRule {}
444
445impl OptimizationRule for SimplifyExprRule {
446    #[allow(clippy::float_cmp)]
447    fn optimize_expr(
448        &mut self,
449        expr_arena: &mut Arena<AExpr>,
450        expr_node: Node,
451        lp_arena: &Arena<IR>,
452        lp_node: Node,
453    ) -> PolarsResult<Option<AExpr>> {
454        let expr = expr_arena.get(expr_node).clone();
455
456        let out = match &expr {
457            // drop_nulls().len() -> len() - null_count()
458            // drop_nulls().count() -> len() - null_count()
459            AExpr::Agg(IRAggExpr::Count(input, _)) => {
460                let input_expr = expr_arena.get(*input);
461                match input_expr {
462                    AExpr::Function {
463                        input,
464                        function: FunctionExpr::DropNulls,
465                        options: _,
466                    } => {
467                        // we should perform optimization only if the original expression is a column
468                        // so in case of disabled CSE, we will not suffer from performance regression
469                        if input.len() == 1 {
470                            let drop_nulls_input_node = input[0].node();
471                            match expr_arena.get(drop_nulls_input_node) {
472                                AExpr::Column(_) => Some(AExpr::BinaryExpr {
473                                    op: Operator::Minus,
474                                    right: expr_arena.add(new_null_count(input)),
475                                    left: expr_arena.add(AExpr::Agg(IRAggExpr::Count(
476                                        drop_nulls_input_node,
477                                        true,
478                                    ))),
479                                }),
480                                _ => None,
481                            }
482                        } else {
483                            None
484                        }
485                    },
486                    _ => None,
487                }
488            },
489            // is_null().sum() -> null_count()
490            // is_not_null().sum() -> len() - null_count()
491            AExpr::Agg(IRAggExpr::Sum(input)) => {
492                let input_expr = expr_arena.get(*input);
493                match input_expr {
494                    AExpr::Function {
495                        input,
496                        function: FunctionExpr::Boolean(BooleanFunction::IsNull),
497                        options: _,
498                    } => Some(new_null_count(input)),
499                    AExpr::Function {
500                        input,
501                        function: FunctionExpr::Boolean(BooleanFunction::IsNotNull),
502                        options: _,
503                    } => {
504                        // we should perform optimization only if the original expression is a column
505                        // so in case of disabled CSE, we will not suffer from performance regression
506                        if input.len() == 1 {
507                            let is_not_null_input_node = input[0].node();
508                            match expr_arena.get(is_not_null_input_node) {
509                                AExpr::Column(_) => Some(AExpr::BinaryExpr {
510                                    op: Operator::Minus,
511                                    right: expr_arena.add(new_null_count(input)),
512                                    left: expr_arena.add(AExpr::Agg(IRAggExpr::Count(
513                                        is_not_null_input_node,
514                                        true,
515                                    ))),
516                                }),
517                                _ => None,
518                            }
519                        } else {
520                            None
521                        }
522                    },
523                    _ => None,
524                }
525            },
526            // lit(left) + lit(right) => lit(left + right)
527            // and null propagation
528            AExpr::BinaryExpr { left, op, right } => {
529                let left_aexpr = expr_arena.get(*left);
530                let right_aexpr = expr_arena.get(*right);
531
532                // lit(left) + lit(right) => lit(left + right)
533                use Operator::*;
534                #[allow(clippy::manual_map)]
535                let out = match op {
536                    Plus => {
537                        match eval_binary_same_type!(left_aexpr, right_aexpr, |l, r| l + r) {
538                            Some(new) => Some(new),
539                            None => {
540                                // try to replace addition of string columns with `concat_str`
541                                #[cfg(all(feature = "strings", feature = "concat_str"))]
542                                {
543                                    string_addition_to_linear_concat(
544                                        lp_arena,
545                                        lp_node,
546                                        expr_arena,
547                                        *left,
548                                        *right,
549                                        left_aexpr,
550                                        right_aexpr,
551                                    )
552                                }
553                                #[cfg(not(all(feature = "strings", feature = "concat_str")))]
554                                {
555                                    None
556                                }
557                            },
558                        }
559                    },
560                    Minus => eval_binary_same_type!(left_aexpr, right_aexpr, |l, r| l - r),
561                    Multiply => eval_binary_same_type!(left_aexpr, right_aexpr, |l, r| l * r),
562                    Divide => {
563                        if let (AExpr::Literal(lit_left), AExpr::Literal(lit_right)) =
564                            (left_aexpr, right_aexpr)
565                        {
566                            match (lit_left, lit_right) {
567                                (LiteralValue::Float32(x), LiteralValue::Float32(y)) => {
568                                    Some(AExpr::Literal(LiteralValue::Float32(x / y)))
569                                },
570                                (LiteralValue::Float64(x), LiteralValue::Float64(y)) => {
571                                    Some(AExpr::Literal(LiteralValue::Float64(x / y)))
572                                },
573                                (LiteralValue::Float(x), LiteralValue::Float(y)) => {
574                                    Some(AExpr::Literal(LiteralValue::Float64(x / y)))
575                                },
576                                #[cfg(feature = "dtype-i8")]
577                                (LiteralValue::Int8(x), LiteralValue::Int8(y)) => {
578                                    Some(AExpr::Literal(LiteralValue::Int8(
579                                        x.wrapping_floor_div_mod(*y).0,
580                                    )))
581                                },
582                                #[cfg(feature = "dtype-i16")]
583                                (LiteralValue::Int16(x), LiteralValue::Int16(y)) => {
584                                    Some(AExpr::Literal(LiteralValue::Int16(
585                                        x.wrapping_floor_div_mod(*y).0,
586                                    )))
587                                },
588                                (LiteralValue::Int32(x), LiteralValue::Int32(y)) => {
589                                    Some(AExpr::Literal(LiteralValue::Int32(
590                                        x.wrapping_floor_div_mod(*y).0,
591                                    )))
592                                },
593                                (LiteralValue::Int64(x), LiteralValue::Int64(y)) => {
594                                    Some(AExpr::Literal(LiteralValue::Int64(
595                                        x.wrapping_floor_div_mod(*y).0,
596                                    )))
597                                },
598                                (LiteralValue::Int(x), LiteralValue::Int(y)) => {
599                                    Some(AExpr::Literal(LiteralValue::Int(
600                                        x.wrapping_floor_div_mod(*y).0,
601                                    )))
602                                },
603                                #[cfg(feature = "dtype-u8")]
604                                (LiteralValue::UInt8(x), LiteralValue::UInt8(y)) => {
605                                    Some(AExpr::Literal(LiteralValue::UInt8(x / y)))
606                                },
607                                #[cfg(feature = "dtype-u16")]
608                                (LiteralValue::UInt16(x), LiteralValue::UInt16(y)) => {
609                                    Some(AExpr::Literal(LiteralValue::UInt16(x / y)))
610                                },
611                                (LiteralValue::UInt32(x), LiteralValue::UInt32(y)) => {
612                                    Some(AExpr::Literal(LiteralValue::UInt32(x / y)))
613                                },
614                                (LiteralValue::UInt64(x), LiteralValue::UInt64(y)) => {
615                                    Some(AExpr::Literal(LiteralValue::UInt64(x / y)))
616                                },
617                                _ => None,
618                            }
619                        } else {
620                            None
621                        }
622                    },
623                    TrueDivide => {
624                        if let (AExpr::Literal(lit_left), AExpr::Literal(lit_right)) =
625                            (left_aexpr, right_aexpr)
626                        {
627                            match (lit_left, lit_right) {
628                                (LiteralValue::Float32(x), LiteralValue::Float32(y)) => {
629                                    Some(AExpr::Literal(LiteralValue::Float32(x / y)))
630                                },
631                                (LiteralValue::Float64(x), LiteralValue::Float64(y)) => {
632                                    Some(AExpr::Literal(LiteralValue::Float64(x / y)))
633                                },
634                                (LiteralValue::Float(x), LiteralValue::Float(y)) => {
635                                    Some(AExpr::Literal(LiteralValue::Float(x / y)))
636                                },
637                                #[cfg(feature = "dtype-i8")]
638                                (LiteralValue::Int8(x), LiteralValue::Int8(y)) => Some(
639                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
640                                ),
641                                #[cfg(feature = "dtype-i16")]
642                                (LiteralValue::Int16(x), LiteralValue::Int16(y)) => Some(
643                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
644                                ),
645                                (LiteralValue::Int32(x), LiteralValue::Int32(y)) => Some(
646                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
647                                ),
648                                (LiteralValue::Int64(x), LiteralValue::Int64(y)) => Some(
649                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
650                                ),
651                                #[cfg(feature = "dtype-u8")]
652                                (LiteralValue::UInt8(x), LiteralValue::UInt8(y)) => Some(
653                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
654                                ),
655                                #[cfg(feature = "dtype-u16")]
656                                (LiteralValue::UInt16(x), LiteralValue::UInt16(y)) => Some(
657                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
658                                ),
659                                (LiteralValue::UInt32(x), LiteralValue::UInt32(y)) => Some(
660                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
661                                ),
662                                (LiteralValue::UInt64(x), LiteralValue::UInt64(y)) => Some(
663                                    AExpr::Literal(LiteralValue::Float64(*x as f64 / *y as f64)),
664                                ),
665                                (LiteralValue::Int(x), LiteralValue::Int(y)) => {
666                                    Some(AExpr::Literal(LiteralValue::Float(*x as f64 / *y as f64)))
667                                },
668                                _ => None,
669                            }
670                        } else {
671                            None
672                        }
673                    },
674                    Modulus => eval_binary_same_type!(left_aexpr, right_aexpr, |l, r| l
675                        .wrapping_floor_div_mod(*r)
676                        .1),
677                    Lt => eval_binary_cmp_same_type!(left_aexpr, <, right_aexpr),
678                    Gt => eval_binary_cmp_same_type!(left_aexpr, >, right_aexpr),
679                    Eq | EqValidity => eval_binary_cmp_same_type!(left_aexpr, ==, right_aexpr),
680                    NotEq | NotEqValidity => {
681                        eval_binary_cmp_same_type!(left_aexpr, !=, right_aexpr)
682                    },
683                    GtEq => eval_binary_cmp_same_type!(left_aexpr, >=, right_aexpr),
684                    LtEq => eval_binary_cmp_same_type!(left_aexpr, <=, right_aexpr),
685                    And | LogicalAnd => eval_bitwise(left_aexpr, right_aexpr, |l, r| l & r),
686                    Or | LogicalOr => eval_bitwise(left_aexpr, right_aexpr, |l, r| l | r),
687                    Xor => eval_bitwise(left_aexpr, right_aexpr, |l, r| l ^ r),
688                    FloorDivide => eval_binary_same_type!(left_aexpr, right_aexpr, |l, r| l
689                        .wrapping_floor_div_mod(*r)
690                        .0),
691                };
692                if out.is_some() {
693                    return Ok(out);
694                }
695
696                None
697            },
698            AExpr::Function {
699                input,
700                function,
701                options,
702                ..
703            } => return optimize_functions(input, function, options, expr_arena),
704            _ => None,
705        };
706        Ok(out)
707    }
708}
709
710#[test]
711#[cfg(feature = "dtype-i8")]
712fn test_expr_to_aexp() {
713    use super::*;
714
715    let expr = Expr::Literal(LiteralValue::Int8(0));
716    let mut arena = Arena::new();
717    let aexpr = to_aexpr(expr, &mut arena).unwrap();
718    assert_eq!(aexpr, Node(0));
719    assert!(matches!(
720        arena.get(aexpr),
721        AExpr::Literal(LiteralValue::Int8(0))
722    ))
723}