vortex_array/compute/
is_constant.rs1use vortex_error::{VortexExpect as _, VortexResult, vortex_bail};
2
3use crate::arrays::{ConstantArray, NullArray};
4use crate::stats::{Precision, Stat, StatsProviderExt};
5use crate::{Array, ArrayExt, Encoding};
6
7pub trait IsConstantFn<A> {
8 fn is_constant(&self, array: A, opts: &IsConstantOpts) -> VortexResult<Option<bool>>;
15}
16
17impl<E: Encoding> IsConstantFn<&dyn Array> for E
18where
19 E: for<'a> IsConstantFn<&'a E::Array>,
20{
21 fn is_constant(&self, array: &dyn Array, opts: &IsConstantOpts) -> VortexResult<Option<bool>> {
22 let array_ref = array
23 .as_any()
24 .downcast_ref::<E::Array>()
25 .vortex_expect("Failed to downcast array");
26 IsConstantFn::is_constant(self, array_ref, opts)
27 }
28}
29
30#[derive(Clone)]
32pub struct IsConstantOpts {
33 pub canonicalize: bool,
35}
36
37impl Default for IsConstantOpts {
38 fn default() -> Self {
39 Self { canonicalize: true }
40 }
41}
42
43pub fn is_constant(array: &dyn Array) -> VortexResult<bool> {
54 let opts = IsConstantOpts::default();
55 is_constant_opts(array, &opts)
56}
57
58pub fn is_constant_opts(array: &dyn Array, opts: &IsConstantOpts) -> VortexResult<bool> {
62 match array.len() {
63 0 => return Ok(false),
65 1 => return Ok(true),
67 _ => {}
68 }
69
70 if array.as_opt::<ConstantArray>().is_some() || array.as_opt::<NullArray>().is_some() {
72 return Ok(true);
73 }
74
75 if let Some(Precision::Exact(value)) = array.statistics().get_as::<bool>(Stat::IsConstant) {
77 return Ok(value);
78 }
79
80 let all_invalid = array.all_invalid()?;
81 if all_invalid {
82 return Ok(true);
83 }
84
85 let all_valid = array.all_valid()?;
86
87 if !all_valid && !all_invalid {
89 return Ok(false);
90 }
91
92 let min = array
94 .statistics()
95 .get_scalar(Stat::Min, array.dtype())
96 .and_then(|p| p.as_exact());
97 let max = array
98 .statistics()
99 .get_scalar(Stat::Max, array.dtype())
100 .and_then(|p| p.as_exact());
101
102 if let Some((min, max)) = min.zip(max) {
103 if min == max {
104 return Ok(true);
105 }
106 }
107
108 debug_assert!(
109 all_valid,
110 "All values must be valid as an invariant of the VTable."
111 );
112 let is_constant = if let Some(vtable_fn) = array.vtable().is_constant_fn() {
113 vtable_fn.is_constant(array, opts)?
114 } else {
115 log::debug!(
116 "No is_constant implementation found for {}",
117 array.encoding()
118 );
119
120 if opts.canonicalize {
121 let array = array.to_canonical()?;
122
123 if let Some(is_constant_fn) = array.as_ref().vtable().is_constant_fn() {
124 is_constant_fn.is_constant(array.as_ref(), opts)?
125 } else {
126 vortex_bail!(
127 "No is_constant function for canonical array: {}",
128 array.as_ref().encoding(),
129 )
130 }
131 } else {
132 None
133 }
134 };
135
136 if let Some(is_constant) = is_constant {
137 array
138 .statistics()
139 .set(Stat::IsConstant, Precision::Exact(is_constant.into()));
140 }
141
142 Ok(is_constant.unwrap_or_default())
143}