1use crate::expr::{AlgebraicAnalysis, BigInt, Expr, ExprNode, ExprNodeId};
58use num_rational::BigRational;
59use num_traits::{One, Signed, ToPrimitive, Zero};
60use std::cmp::Ordering;
61use std::collections::{BTreeMap, HashMap};
62
63const ANTICHAIN_CAP: usize = 32;
65
66#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
68enum ExpBase {
69 Rational(BigRational),
71 Natural,
73}
74
75impl ExpBase {
76 fn directly_comparable_value(&self) -> Option<&BigRational> {
77 match self {
78 ExpBase::Rational(base) => Some(base),
79 ExpBase::Natural => None,
80 }
81 }
82
83 fn direction(&self) -> Ordering {
84 match self {
85 ExpBase::Rational(base) => base.cmp(&BigRational::one()),
86 ExpBase::Natural => Ordering::Greater,
87 }
88 }
89
90 fn coefficient_cmp(&self, a: &BigRational, b: &BigRational) -> Ordering {
91 if self.direction() == Ordering::Greater {
92 a.cmp(b)
93 } else {
94 a.cmp(b).reverse()
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
102struct VariableGrowth {
103 exp: BTreeMap<ExpBase, BigRational>,
104 poly: BigRational,
105 log: u32,
106}
107
108impl VariableGrowth {
109 fn empty() -> Self {
110 Self {
111 exp: BTreeMap::new(),
112 poly: BigRational::zero(),
113 log: 0,
114 }
115 }
116
117 fn exponential(base: ExpBase, coefficient: BigRational) -> Self {
118 Self {
119 exp: BTreeMap::from([(base, coefficient)]),
120 poly: BigRational::zero(),
121 log: 0,
122 }
123 }
124
125 fn polynomial(degree: BigRational) -> Self {
126 Self {
127 exp: BTreeMap::new(),
128 poly: degree,
129 log: 0,
130 }
131 }
132
133 fn logarithmic(power: u32) -> Self {
134 Self {
135 exp: BTreeMap::new(),
136 poly: BigRational::zero(),
137 log: power,
138 }
139 }
140
141 fn is_empty(&self) -> bool {
142 self.exp.is_empty() && self.poly.is_zero() && self.log == 0
143 }
144
145 fn mul(&self, other: &Self) -> Result<Self, GrowthFailure> {
146 let mut result = self.clone();
147 for (base, coefficient) in &other.exp {
148 *result
149 .exp
150 .entry(base.clone())
151 .or_insert_with(BigRational::zero) += coefficient;
152 }
153 result.exp.retain(|_, coefficient| !coefficient.is_zero());
154 result.poly += &other.poly;
155 result.log = result.log.checked_add(other.log).ok_or_else(|| {
156 GrowthFailure::RepresentedExponentOutOfRange(format!("{} + {}", self.log, other.log))
157 })?;
158 Ok(result)
159 }
160
161 fn pow(&self, power: &BigRational) -> Result<Self, GrowthFailure> {
162 let exp = self
163 .exp
164 .iter()
165 .filter_map(|(base, coefficient)| {
166 let coefficient = coefficient * power;
167 (!coefficient.is_zero()).then(|| (base.clone(), coefficient))
168 })
169 .collect();
170 let poly = &self.poly * power;
171 let scaled_log = BigRational::from_integer(BigInt::from(self.log)) * power;
172 let rounded =
173 (scaled_log.numer() + scaled_log.denom() - BigInt::one()) / scaled_log.denom();
174 let Some(log) = rounded.to_u32() else {
175 return Err(GrowthFailure::RepresentedExponentOutOfRange(
176 scaled_log.to_string(),
177 ));
178 };
179 Ok(Self { exp, poly, log })
180 }
181
182 fn cmp_exp(&self, other: &Self) -> Option<Ordering> {
183 let mut left_count = 0;
184 let mut right_count = 0;
185 let mut left_single: Option<(&ExpBase, BigRational)> = None;
186 let mut right_single: Option<(&ExpBase, BigRational)> = None;
187
188 for (base, a) in &self.exp {
189 if let Some(b) = other.exp.get(base) {
190 match base.coefficient_cmp(a, b) {
191 Ordering::Equal => {}
192 Ordering::Greater => {
193 left_count += 1;
194 left_single = Some((base, a - b));
195 }
196 Ordering::Less => {
197 right_count += 1;
198 right_single = Some((base, b - a));
199 }
200 }
201 } else {
202 left_count += 1;
203 left_single = Some((base, a.clone()));
204 }
205 }
206
207 for (base, coefficient) in &other.exp {
208 if !self.exp.contains_key(base) {
209 right_count += 1;
210 right_single = Some((base, coefficient.clone()));
211 }
212 }
213
214 match (left_count, right_count) {
215 (0, 0) => Some(Ordering::Equal),
216 (0, _) => Some(Ordering::Less),
217 (_, 0) => Some(Ordering::Greater),
218 (1, 1) => {
219 let (a_base, a_coefficient) = left_single?;
220 let (b_base, b_coefficient) = right_single?;
221 Self::cmp_single_factor(a_base, &a_coefficient, b_base, &b_coefficient)
222 }
223 _ => None,
224 }
225 }
226
227 fn cmp_growth(&self, other: &Self) -> Option<Ordering> {
228 let exponential = self.cmp_exp(other)?;
229 Some(if exponential == Ordering::Equal {
230 self.poly.cmp(&other.poly).then(self.log.cmp(&other.log))
231 } else {
232 exponential
233 })
234 }
235
236 fn cmp_single_factor(
237 a_base: &ExpBase,
238 a_coefficient: &BigRational,
239 b_base: &ExpBase,
240 b_coefficient: &BigRational,
241 ) -> Option<Ordering> {
242 if a_base == b_base {
243 return Some(a_base.coefficient_cmp(a_coefficient, b_coefficient));
244 }
245
246 if a_coefficient == b_coefficient {
247 match (a_base, b_base) {
248 (ExpBase::Natural, ExpBase::Rational(_)) => {
249 let base = b_base.directly_comparable_value()?;
250 if base <= &BigRational::from_integer(2.into()) {
251 return Some(Ordering::Greater);
252 }
253 if base >= &BigRational::from_integer(3.into()) {
254 return Some(Ordering::Less);
255 }
256 return None;
257 }
258 (ExpBase::Rational(_), ExpBase::Natural) => {
259 return Self::cmp_single_factor(b_base, b_coefficient, a_base, a_coefficient)
260 .map(Ordering::reverse);
261 }
262 _ => {}
263 }
264 }
265
266 let (a_base, b_base) = (
267 a_base.directly_comparable_value()?,
268 b_base.directly_comparable_value()?,
269 );
270 if a_coefficient == b_coefficient {
271 let base_order = a_base.cmp(b_base);
272 return if a_coefficient.is_positive() {
273 Some(base_order)
274 } else {
275 Some(base_order.reverse())
276 };
277 }
278
279 if a_base > &BigRational::one() && b_base > &BigRational::one() {
280 match (a_base.cmp(b_base), a_coefficient.cmp(b_coefficient)) {
281 (Ordering::Greater | Ordering::Equal, Ordering::Greater | Ordering::Equal) => {
282 Some(Ordering::Greater)
283 }
284 (Ordering::Less | Ordering::Equal, Ordering::Less | Ordering::Equal) => {
285 Some(Ordering::Less)
286 }
287 _ => None,
288 }
289 } else if a_base < &BigRational::one() && b_base < &BigRational::one() {
290 match (a_base.cmp(b_base), a_coefficient.cmp(b_coefficient)) {
291 (Ordering::Less | Ordering::Equal, Ordering::Less | Ordering::Equal) => {
292 Some(Ordering::Greater)
293 }
294 (Ordering::Greater | Ordering::Equal, Ordering::Greater | Ordering::Equal) => {
295 Some(Ordering::Less)
296 }
297 _ => None,
298 }
299 } else {
300 None
301 }
302 }
303}
304
305#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
309struct GrowthTerm {
310 variables: BTreeMap<Box<str>, VariableGrowth>,
311}
312
313#[derive(Clone, Debug, PartialEq, Eq)]
315pub struct Growth(GrowthState);
316
317#[derive(Clone, Debug, PartialEq, Eq)]
318enum GrowthState {
319 Known(Vec<GrowthTerm>),
320 Unknown(Vec<GrowthFailure>),
323}
324
325#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)]
327pub enum GrowthFailure {
328 #[error("invalid or unproved constant domain for {expression}")]
329 InvalidConstantDomain { expression: String },
330 #[error("negative exponent is unsupported: {0}")]
331 NegativeExponent(String),
332 #[error("nonlinear exponent is unsupported: {0}")]
333 NonlinearExponent(String),
334 #[error("variable base and exponent are unsupported: {0}")]
335 VariableBaseAndExponent(String),
336 #[error("factorial of a nonconstant expression is unsupported: {0}")]
337 FactorialOfNonconstant(String),
338 #[error("invalid exponential base: {0}")]
339 InvalidExponentialBase(String),
340 #[error("represented exponent is outside the growth domain: {0}")]
341 RepresentedExponentOutOfRange(String),
342 #[error(
343 "exponential factor {base}^({coefficient} * {variable}) decreases as {variable} grows"
344 )]
345 DecayingExponential {
346 base: String,
347 variable: String,
348 coefficient: String,
349 },
350 #[error("missing substitution for {0}")]
351 MissingSubstitution(String),
352 #[error("Big-O antichain has {terms} terms, exceeding the limit of {limit}")]
353 AntichainLimitExceeded { limit: usize, terms: usize },
354}
355
356impl GrowthTerm {
357 fn one() -> Self {
359 GrowthTerm {
360 variables: BTreeMap::new(),
361 }
362 }
363
364 fn insert(&mut self, variable: Box<str>, growth: VariableGrowth) {
365 if !growth.is_empty() {
366 self.variables.insert(variable, growth);
367 }
368 }
369
370 fn pow(&self, k: &BigRational) -> Result<GrowthTerm, GrowthFailure> {
374 let mut r = GrowthTerm::one();
375 for (variable, growth) in &self.variables {
376 r.insert(variable.clone(), growth.pow(k)?);
377 }
378 Ok(r)
379 }
380
381 fn mul(&self, other: &GrowthTerm) -> Result<GrowthTerm, GrowthFailure> {
383 let mut t = self.clone();
384 for (variable, growth) in &other.variables {
385 let combined = match t.variables.get(variable) {
386 Some(current) => current.mul(growth)?,
387 None => growth.clone(),
388 };
389 if combined.is_empty() {
390 t.variables.remove(variable);
391 } else {
392 t.variables.insert(variable.clone(), combined);
393 }
394 }
395 Ok(t)
396 }
397
398 fn cmp(&self, other: &GrowthTerm) -> Option<Ordering> {
404 let mut saw_gt = false;
405 let mut saw_lt = false;
406 let empty = VariableGrowth::empty();
407 let mut left = self.variables.iter().peekable();
408 let mut right = other.variables.iter().peekable();
409 loop {
410 let (a, b) = match (left.peek(), right.peek()) {
411 (None, None) => break,
412 (Some((left_variable, _)), Some((right_variable, _))) => {
413 match left_variable.cmp(right_variable) {
414 Ordering::Less => (left.next().unwrap().1, &empty),
415 Ordering::Greater => (&empty, right.next().unwrap().1),
416 Ordering::Equal => (left.next().unwrap().1, right.next().unwrap().1),
417 }
418 }
419 (Some(_), None) => (left.next().unwrap().1, &empty),
420 (None, Some(_)) => (&empty, right.next().unwrap().1),
421 };
422 let order = a.cmp_growth(b)?;
423 match order {
424 Ordering::Greater => saw_gt = true,
425 Ordering::Less => saw_lt = true,
426 Ordering::Equal => {}
427 }
428 }
429 match (saw_gt, saw_lt) {
430 (true, true) => None,
431 (true, false) => Some(Ordering::Greater),
432 (false, true) => Some(Ordering::Less),
433 (false, false) => Some(Ordering::Equal),
434 }
435 }
436
437 fn dominates(&self, other: &GrowthTerm) -> bool {
440 matches!(self.cmp(other), Some(Ordering::Greater))
441 }
442
443 fn dominates_or_eq(&self, other: &GrowthTerm) -> bool {
445 matches!(
446 self.cmp(other),
447 Some(Ordering::Greater) | Some(Ordering::Equal)
448 )
449 }
450}
451
452impl Growth {
453 pub(crate) fn unknown(failure: GrowthFailure) -> Self {
454 Self(GrowthState::Unknown(vec![failure]))
455 }
456
457 pub fn failures(&self) -> Option<&[GrowthFailure]> {
458 match &self.0 {
459 GrowthState::Known(_) => None,
460 GrowthState::Unknown(failures) => Some(failures),
461 }
462 }
463
464 pub fn from_expr(expr: &Expr) -> Growth {
466 let analysis = AlgebraicAnalysis::new(&[expr]);
467 Self::from_analysis(expr, &analysis)
468 }
469
470 pub(crate) fn from_analysis(expr: &Expr, analysis: &AlgebraicAnalysis) -> Growth {
471 growth_from_analysis(expr, analysis, &mut HashMap::new())
472 }
473
474 pub fn dominates(&self, other: &Growth) -> bool {
480 match (&self.0, &other.0) {
481 (GrowthState::Known(a), GrowthState::Known(b)) => {
482 b.iter().all(|tb| a.iter().any(|ta| ta.dominates_or_eq(tb)))
483 }
484 _ => false,
485 }
486 }
487
488 pub fn to_expr(&self) -> Option<Expr> {
495 match &self.0 {
496 GrowthState::Unknown(_) => None,
497 GrowthState::Known(terms) => {
498 if terms.is_empty() {
499 return Some(Expr::integer(1));
500 }
501 let mut it = terms.iter().map(term_to_expr);
502 let mut acc = it.next().unwrap();
503 for e in it {
504 acc = acc + e;
505 }
506 Some(acc)
507 }
508 }
509 }
510
511 pub fn to_big_o(&self) -> String {
517 match self.to_expr() {
518 Some(e) => format!("O({e})"),
519 None => "O(?)".to_string(),
520 }
521 }
522}
523
524fn growth_from_analysis(
525 expression: &Expr,
526 analysis: &AlgebraicAnalysis,
527 memo: &mut HashMap<ExprNodeId, Growth>,
528) -> Growth {
529 if let Some(growth) = memo.get(&expression.node_identity()) {
530 return growth.clone();
531 }
532
533 let facts = analysis.facts(expression);
534 if facts.is_constant {
535 let growth = if facts.constant_domain == Some(true) {
536 constant_growth()
537 } else {
538 unknown(GrowthFailure::InvalidConstantDomain {
539 expression: expression.to_string(),
540 })
541 };
542 memo.insert(expression.node_identity(), growth.clone());
543 return growth;
544 }
545
546 let growth = match expression.node() {
547 ExprNode::Const(_) => unreachable!("constants are handled before node projection"),
548 ExprNode::Var(variable) => {
549 let mut term = GrowthTerm::one();
550 term.insert(
551 variable.as_str().into(),
552 VariableGrowth::polynomial(BigRational::one()),
553 );
554 exact_growth(vec![term])
555 }
556 ExprNode::Add(values) => values
557 .iter()
558 .map(|value| growth_from_analysis(value, analysis, memo))
559 .reduce(add)
560 .expect("normalized sum has at least two terms"),
561 ExprNode::Mul(values) => values
562 .iter()
563 .map(|value| growth_from_analysis(value, analysis, memo))
564 .reduce(mul)
565 .expect("normalized product has at least two factors"),
566 ExprNode::Pow(base, exponent) => {
567 let base_facts = analysis.facts(base);
568 let exponent_facts = analysis.facts(exponent);
569 if base_facts.is_constant && base_facts.constant_domain != Some(true) {
570 unknown(GrowthFailure::InvalidConstantDomain {
571 expression: base.to_string(),
572 })
573 } else if exponent_facts.is_constant && exponent_facts.constant_domain != Some(true) {
574 unknown(GrowthFailure::InvalidConstantDomain {
575 expression: exponent.to_string(),
576 })
577 } else if let Some(power) = exponent_facts.exact_rational.as_ref() {
578 if power.is_negative() {
579 unknown(GrowthFailure::NegativeExponent(exponent.to_string()))
580 } else {
581 pow_const(growth_from_analysis(base, analysis, memo), power)
582 }
583 } else if let ExprNode::Exp(argument) = base.node() {
584 match analysis.facts(argument).exact_rational.as_ref() {
585 Some(coefficient) => exponential(
586 ExpBase::Natural,
587 scale_growth_linear(exponent_facts.linear.clone(), coefficient),
588 exponent,
589 ),
590 None => unknown(GrowthFailure::InvalidExponentialBase(base.to_string())),
591 }
592 } else if let Some(base_value) = base_facts.exact_rational.as_ref() {
593 if base_value.is_positive() {
594 exponential(
595 ExpBase::Rational(base_value.clone()),
596 growth_linear(exponent_facts.linear.clone()),
597 exponent,
598 )
599 } else {
600 unknown(GrowthFailure::InvalidExponentialBase(base.to_string()))
601 }
602 } else if base_facts.is_constant {
603 unknown(GrowthFailure::InvalidExponentialBase(base.to_string()))
604 } else {
605 unknown(GrowthFailure::VariableBaseAndExponent(
606 expression.to_string(),
607 ))
608 }
609 }
610 ExprNode::Exp(value) => {
611 let value_growth = growth_from_analysis(value, analysis, memo);
612 if value_growth.failures().is_some() {
613 value_growth
614 } else {
615 exponential(
616 ExpBase::Natural,
617 growth_linear(analysis.facts(value).linear.clone()),
618 expression,
619 )
620 }
621 }
622 ExprNode::Log(value) => log_growth(growth_from_analysis(value, analysis, memo)),
623 ExprNode::Factorial(_) => unknown(GrowthFailure::FactorialOfNonconstant(
624 expression.to_string(),
625 )),
626 };
627 memo.insert(expression.node_identity(), growth.clone());
628 growth
629}
630
631fn growth_linear(
632 linear: Option<BTreeMap<crate::expr::Symbol, BigRational>>,
633) -> Option<BTreeMap<Box<str>, BigRational>> {
634 Some(
635 linear?
636 .into_iter()
637 .map(|(symbol, coefficient)| (symbol.as_str().into(), coefficient))
638 .collect(),
639 )
640}
641
642fn scale_growth_linear(
643 linear: Option<BTreeMap<crate::expr::Symbol, BigRational>>,
644 coefficient: &BigRational,
645) -> Option<BTreeMap<Box<str>, BigRational>> {
646 Some(
647 linear?
648 .into_iter()
649 .map(|(symbol, value)| (symbol.as_str().into(), coefficient * value))
650 .collect(),
651 )
652}
653fn constant_growth() -> Growth {
654 exact_growth(vec![GrowthTerm::one()])
655}
656
657fn unknown(failure: GrowthFailure) -> Growth {
658 Growth::unknown(failure)
659}
660
661fn merge_unknown(left: Growth, right: Growth) -> Growth {
662 let mut failures = Vec::new();
663 if let GrowthState::Unknown(left) = left.0 {
664 failures.extend(left);
665 }
666 if let GrowthState::Unknown(right) = right.0 {
667 failures.extend(right);
668 }
669 failures.sort();
670 failures.dedup();
671 Growth(GrowthState::Unknown(failures))
672}
673
674fn term_to_expr(t: &GrowthTerm) -> Expr {
676 let mut factors: Vec<Expr> = Vec::new();
677 for (variable, growth) in &t.variables {
678 factors.extend(
679 growth
680 .exp
681 .iter()
682 .map(|(base, coefficient)| exp_factor(variable, base, coefficient)),
683 );
684 if !growth.poly.is_zero() {
685 factors.push(poly_factor(variable, &growth.poly));
686 }
687 if growth.log != 0 {
688 factors.push(log_factor(variable, growth.log));
689 }
690 }
691 let mut it = factors.into_iter();
692 match it.next() {
693 None => Expr::integer(1),
694 Some(first) => it.fold(first, |acc, f| acc * f),
695 }
696}
697
698fn exp_factor(v: &str, base: &ExpBase, coefficient: &BigRational) -> Expr {
700 let exponent = if coefficient.is_one() {
701 Expr::variable(v)
702 } else {
703 Expr::constant(coefficient.clone()) * Expr::variable(v)
704 };
705 match base {
706 ExpBase::Rational(base) => Expr::pow(Expr::constant(base.clone()), exponent),
707 ExpBase::Natural => Expr::exp(exponent),
708 }
709}
710
711fn poly_factor(v: &str, degree: &BigRational) -> Expr {
713 if degree.is_one() {
714 Expr::variable(v)
715 } else {
716 Expr::pow(Expr::variable(v), Expr::constant(degree.clone()))
717 }
718}
719
720fn log_factor(v: &str, power: u32) -> Expr {
722 let log = Expr::log(Expr::variable(v));
723 if power == 1 {
724 log
725 } else {
726 Expr::pow(log, Expr::integer(power))
727 }
728}
729
730fn prune(mut terms: Vec<GrowthTerm>) -> Vec<GrowthTerm> {
734 terms.sort();
738 let mut result: Vec<GrowthTerm> = Vec::new();
739 for t in terms {
740 if result.iter().any(|r| r.dominates_or_eq(&t)) {
741 continue;
742 }
743 result.retain(|r| !t.dominates(r));
744 result.push(t);
745 }
746 result
747}
748
749fn exact_growth(terms: Vec<GrowthTerm>) -> Growth {
750 finish_growth(terms)
751}
752
753fn finish_growth(terms: Vec<GrowthTerm>) -> Growth {
754 let terms = prune(terms);
755 if terms.len() > ANTICHAIN_CAP {
756 return unknown(GrowthFailure::AntichainLimitExceeded {
757 limit: ANTICHAIN_CAP,
758 terms: terms.len(),
759 });
760 }
761 Growth(GrowthState::Known(terms))
762}
763
764fn add(a: Growth, b: Growth) -> Growth {
766 if a.failures().is_some() || b.failures().is_some() {
767 return merge_unknown(a, b);
768 }
769 let mut terms = into_terms(a);
770 terms.extend(into_terms(b));
771 finish_growth(terms)
772}
773
774fn mul(a: Growth, b: Growth) -> Growth {
776 if a.failures().is_some() || b.failures().is_some() {
777 return merge_unknown(a, b);
778 }
779 let x = into_terms(a);
780 let y = into_terms(b);
781 let mut product = Vec::with_capacity(x.len() * y.len());
782 for tx in &x {
783 for ty in &y {
784 match tx.mul(ty) {
785 Ok(term) => product.push(term),
786 Err(failure) => return unknown(failure),
787 }
788 }
789 }
790 finish_growth(product)
791}
792
793fn pow_const(g: Growth, k: &BigRational) -> Growth {
795 match g.0 {
796 GrowthState::Unknown(failures) => Growth(GrowthState::Unknown(failures)),
797 GrowthState::Known(terms) => match terms.iter().map(|term| term.pow(k)).collect() {
798 Ok(terms) => finish_growth(terms),
799 Err(failure) => unknown(failure),
800 },
801 }
802}
803
804fn into_terms(growth: Growth) -> Vec<GrowthTerm> {
805 match growth.0 {
806 GrowthState::Known(terms) => terms,
807 GrowthState::Unknown(_) => unreachable!("unknown growth is handled before term access"),
808 }
809}
810
811fn exponential(
813 base: ExpBase,
814 linear: Option<BTreeMap<Box<str>, BigRational>>,
815 exponent: &Expr,
816) -> Growth {
817 let direction = base.direction();
818 if direction == Ordering::Equal {
819 return exact_growth(vec![GrowthTerm::one()]);
821 }
822 match linear {
823 None => unknown(GrowthFailure::NonlinearExponent(exponent.to_string())),
824 Some(coeffs) => {
825 let mut term = GrowthTerm::one();
826 for (v, coeff) in coeffs {
827 if (direction == Ordering::Greater && coeff.is_positive())
828 || (direction == Ordering::Less && coeff.is_negative())
829 {
830 term.insert(v, VariableGrowth::exponential(base.clone(), coeff));
831 } else if !coeff.is_zero() {
832 return unknown(GrowthFailure::DecayingExponential {
833 base: match &base {
834 ExpBase::Rational(value) => value.to_string(),
835 ExpBase::Natural => "e".to_string(),
836 },
837 variable: v.to_string(),
838 coefficient: coeff.to_string(),
839 });
840 }
841 }
842 exact_growth(vec![term])
843 }
844 }
845}
846
847fn log_growth(g: Growth) -> Growth {
851 match g.0 {
852 GrowthState::Unknown(failures) => Growth(GrowthState::Unknown(failures)),
853 GrowthState::Known(terms) => {
854 let mut out = Vec::new();
855 for t in &terms {
856 out.extend(log_term(t));
857 }
858 if out.is_empty() {
859 out.push(GrowthTerm::one()); }
861 finish_growth(out)
862 }
863 }
864}
865
866fn log_term(t: &GrowthTerm) -> Vec<GrowthTerm> {
873 let mut out = Vec::new();
874 for (variable, growth) in &t.variables {
875 if !growth.exp.is_empty() {
876 let mut term = GrowthTerm::one();
877 term.insert(
878 variable.clone(),
879 VariableGrowth::polynomial(BigRational::one()),
880 );
881 out.push(term);
882 }
883 if growth.poly.is_positive() || growth.log != 0 {
884 let mut term = GrowthTerm::one();
885 term.insert(variable.clone(), VariableGrowth::logarithmic(1));
886 out.push(term);
887 }
888 }
889 if out.is_empty() {
891 out.push(GrowthTerm::one());
892 }
893 out
894}
895
896#[cfg(test)]
897#[path = "unit_tests/growth.rs"]
898mod tests;