Skip to main content

litex/verify/verify_builtin_rules/
number_compare.rs

1use super::order_normalize::normalize_positive_order_atomic_fact;
2use crate::prelude::*;
3
4impl Runtime {
5    // Lit `know` facts for the nonnegative / positive cone under field operations used to live in
6    // `BUILTIN_ENV_CODE_FOR_FUNDAMENTAL_COMPARISON` (`fundamental_comparison.rs`). Those fragments
7    // were removed as redundant; the same mathematics is checked here on normalized `0 <=` / `0 <`
8    // goals (possibly after `normalize_positive_order_atomic_fact`):
9    // - Chained `+`: `0 <= a + b + …` from `0 <=` on each peeled summand; `0 < a + b + …` from
10    //   `(0 < a ∧ 0 <= b) ∨ (0 <= a ∧ 0 < b)` at each binary `+`.
11    // - Powers: literal even integer exponent ⇒ `0 <= base^n`; literal integer exponent and `0 <= base`
12    //   (or `0 < base` if exponent < 0) ⇒ `0 <= base^n`; `a * a` with equal factors; `0 < base^exp`
13    //   from `0 < base` and `exp in R`.
14    // - Products and quotients: `0 <= a * b`, `0 < a * b`, `0 <= a / b` (denominator strictly
15    //   positive), `0 < a / b`, each with recursive sub-goals on operands.
16    // The Lit environment still records order via differences (`a <= b` iff `0 <= b - a`, etc.) and
17    // `a != 0 ⇒ 0 < a^2` (strict square). This path also bridges `0 <= u - v` / `0 < u - v` to `v <= u` / `v < u`.
18    // Algebraic closure (+, -, *, /) on general `a <= b` / `a < b` is in `order_algebra_builtin.rs`.
19    pub fn verify_order_atomic_fact_numeric_builtin_only(
20        &mut self,
21        atomic_fact: &AtomicFact,
22    ) -> Result<StmtResult, RuntimeError> {
23        let vs = VerifyState::new(0, true);
24        if let Some(result) =
25            self.try_verify_order_nonnegative_from_membership_in_n(atomic_fact, &vs)?
26        {
27            return Ok(result);
28        }
29        if let Some(result) =
30            self.try_verify_order_one_le_from_membership_in_n_pos(atomic_fact, &vs)?
31        {
32            return Ok(result);
33        }
34        if let Some(result) =
35            self.try_verify_order_one_le_from_membership_in_n_and_nonzero(atomic_fact, &vs)?
36        {
37            return Ok(result);
38        }
39        if let Some(result) = self.try_verify_order_opposite_sign_mul_minus_one(atomic_fact, &vs)? {
40            return Ok(result);
41        }
42        if let Some(result) = self.verify_order_from_known_negated_complement(atomic_fact)? {
43            return Ok(result);
44        }
45        if let Some(result) = self.verify_negated_order_from_known_equivalent_order(atomic_fact)? {
46            return Ok(result);
47        }
48        if let Some(result) = self.verify_order_algebra_structural_builtin_rule(atomic_fact)? {
49            return Ok(result);
50        }
51        if let Some(result) = self.verify_zero_le_abs_builtin_rule(atomic_fact)? {
52            return Ok(result);
53        }
54        if let Some(result) = self.verify_abs_order_builtin_rule(atomic_fact)? {
55            return Ok(result);
56        }
57        if let Some(result) =
58            self.verify_zero_order_on_sub_from_two_sided_order_builtin_rule(atomic_fact)?
59        {
60            return Ok(result);
61        }
62        if let Some(result) =
63            self.verify_zero_le_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
64        {
65            return Ok(result);
66        }
67        if let Some(result) =
68            self.verify_zero_lt_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
69        {
70            return Ok(result);
71        }
72        if let Some(result) = self.verify_zero_le_even_integer_pow_builtin_rule(atomic_fact)? {
73            return Ok(result);
74        }
75        if let Some(result) =
76            self.verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(atomic_fact)?
77        {
78            return Ok(result);
79        }
80        if let Some(result) =
81            self.verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
82        {
83            return Ok(result);
84        }
85        if let Some(result) =
86            self.verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
87        {
88            return Ok(result);
89        }
90        if let Some(result) =
91            self.verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(atomic_fact)?
92        {
93            return Ok(result);
94        }
95        if let Some(result) =
96            self.verify_zero_le_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
97        {
98            return Ok(result);
99        }
100        if let Some(result) =
101            self.verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
102        {
103            return Ok(result);
104        }
105        if let Some(result) =
106            self.verify_zero_le_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
107        {
108            return Ok(result);
109        }
110        if let Some(result) =
111            self.verify_zero_lt_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
112        {
113            return Ok(result);
114        }
115
116        if let AtomicFact::LessEqualFact(less_equal_fact) = atomic_fact {
117            if less_equal_fact.left.to_string() == less_equal_fact.right.to_string() {
118                return Ok(StmtResult::FactualStmtSuccess(
119                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
120                        less_equal_fact.clone().into(),
121                        "less_equal_fact_equal".to_string(),
122                        Vec::new(),
123                    ),
124                ));
125            }
126            let strict_fact: Fact = LessFact::new(
127                less_equal_fact.left.clone(),
128                less_equal_fact.right.clone(),
129                less_equal_fact.line_file.clone(),
130            )
131            .into();
132            let strict_key = strict_fact.to_string();
133            let (cache_ok, _) = self.cache_known_facts_contains(&strict_key);
134            if cache_ok {
135                return Ok(StmtResult::FactualStmtSuccess(
136                    FactualStmtSuccess::new_with_verified_by_known_fact(
137                        less_equal_fact.clone().into(),
138                        VerifiedByResult::cited_fact(
139                            less_equal_fact.clone().into(),
140                            strict_fact,
141                            None,
142                        ),
143                        Vec::new(),
144                    ),
145                ));
146            }
147        }
148        if let AtomicFact::GreaterEqualFact(greater_equal_fact) = atomic_fact {
149            if greater_equal_fact.left.to_string() == greater_equal_fact.right.to_string() {
150                return Ok(StmtResult::FactualStmtSuccess(
151                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
152                        greater_equal_fact.clone().into(),
153                        "greater_equal_fact_equal".to_string(),
154                        Vec::new(),
155                    ),
156                ));
157            }
158        }
159        if let Some(true) = self.verify_number_comparison_builtin_rule(atomic_fact) {
160            Ok(StmtResult::FactualStmtSuccess(
161                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
162                    atomic_fact.clone().into(),
163                    "number comparison".to_string(),
164                    Vec::new(),
165                ),
166            ))
167        } else {
168            Ok(StmtResult::StmtUnknown(StmtUnknown::new()))
169        }
170    }
171
172    pub fn verify_number_comparison_builtin_rule(&self, atomic_fact: &AtomicFact) -> Option<bool> {
173        let normalized = normalize_positive_order_atomic_fact(atomic_fact)?;
174        match normalized {
175            AtomicFact::LessFact(less_fact) => {
176                if let Some(calculated_number_string_pair) =
177                    self.calculate_obj_pair_to_number_strings(&less_fact.left, &less_fact.right)
178                {
179                    return Some(matches!(
180                        compare_number_strings(
181                            &calculated_number_string_pair.0,
182                            &calculated_number_string_pair.1
183                        ),
184                        NumberCompareResult::Less
185                    ));
186                }
187                self.try_verify_numeric_order_via_div_elimination(
188                    &less_fact.left,
189                    &less_fact.right,
190                    false,
191                )
192            }
193            AtomicFact::LessEqualFact(less_equal_fact) => {
194                if let Some(calculated_number_string_pair) = self
195                    .calculate_obj_pair_to_number_strings(
196                        &less_equal_fact.left,
197                        &less_equal_fact.right,
198                    )
199                {
200                    let compare_result = compare_number_strings(
201                        &calculated_number_string_pair.0,
202                        &calculated_number_string_pair.1,
203                    );
204                    return Some(matches!(
205                        compare_result,
206                        NumberCompareResult::Less | NumberCompareResult::Equal
207                    ));
208                }
209                self.try_verify_numeric_order_via_div_elimination(
210                    &less_equal_fact.left,
211                    &less_equal_fact.right,
212                    true,
213                )
214            }
215            _ => None,
216        }
217    }
218}
219
220pub enum NumberCompareResult {
221    Less,
222    Equal,
223    Greater,
224}
225
226/// Compare a normalized decimal string (same shape as [`Number::normalized_value`]) to `"0"`.
227pub fn compare_normalized_number_str_to_zero(number_value: &str) -> NumberCompareResult {
228    compare_number_strings(number_value.trim(), "0")
229}
230
231fn parse_number_parts_for_comparison(number_value: &str) -> (bool, Vec<u8>, Vec<u8>) {
232    let trimmed_number_value = number_value.trim();
233    let (is_negative, magnitude_string) = if trimmed_number_value.starts_with('-') {
234        (true, trimmed_number_value[1..].trim())
235    } else {
236        (false, trimmed_number_value)
237    };
238
239    let (integer_part_string, fractional_part_string) = match magnitude_string.find('.') {
240        Some(dot_index) => (
241            &magnitude_string[..dot_index],
242            &magnitude_string[dot_index + 1..],
243        ),
244        None => (magnitude_string, ""),
245    };
246
247    let mut integer_digits: Vec<u8> = Vec::new();
248    if integer_part_string.is_empty() {
249        integer_digits.push(0);
250    } else {
251        for current_char in integer_part_string.chars() {
252            if current_char.is_ascii_digit() {
253                integer_digits.push(current_char as u8 - b'0');
254            }
255        }
256        if integer_digits.is_empty() {
257            integer_digits.push(0);
258        }
259    }
260
261    let mut fractional_digits: Vec<u8> = Vec::new();
262    for current_char in fractional_part_string.chars() {
263        if current_char.is_ascii_digit() {
264            fractional_digits.push(current_char as u8 - b'0');
265        }
266    }
267
268    (is_negative, integer_digits, fractional_digits)
269}
270
271fn digits_are_all_zero(digits: &[u8]) -> bool {
272    for digit in digits {
273        if *digit != 0 {
274            return false;
275        }
276    }
277    true
278}
279
280fn normalized_decimal_string_is_integer(number_value: &str) -> bool {
281    let (_, _integer_digits, fractional_digits) = parse_number_parts_for_comparison(number_value);
282    digits_are_all_zero(&fractional_digits)
283}
284
285pub(crate) fn normalized_decimal_string_is_even_integer(number_value: &str) -> bool {
286    if !normalized_decimal_string_is_integer(number_value) {
287        return false;
288    }
289    let (_is_negative, integer_digits, _fractional_digits) =
290        parse_number_parts_for_comparison(number_value);
291    let last_digit = integer_digits.last().copied().unwrap_or(0);
292    last_digit % 2 == 0
293}
294
295fn first_non_zero_integer_digit_index(integer_digits: &[u8]) -> usize {
296    let mut current_index = 0;
297    while current_index + 1 < integer_digits.len() && integer_digits[current_index] == 0 {
298        current_index += 1;
299    }
300    current_index
301}
302
303fn compare_non_negative_decimal_parts(
304    left_integer_digits: &[u8],
305    left_fractional_digits: &[u8],
306    right_integer_digits: &[u8],
307    right_fractional_digits: &[u8],
308) -> NumberCompareResult {
309    let left_integer_start_index = first_non_zero_integer_digit_index(left_integer_digits);
310    let right_integer_start_index = first_non_zero_integer_digit_index(right_integer_digits);
311
312    let left_effective_integer_len = left_integer_digits.len() - left_integer_start_index;
313    let right_effective_integer_len = right_integer_digits.len() - right_integer_start_index;
314    if left_effective_integer_len < right_effective_integer_len {
315        return NumberCompareResult::Less;
316    }
317    if left_effective_integer_len > right_effective_integer_len {
318        return NumberCompareResult::Greater;
319    }
320
321    let mut integer_index = 0;
322    while integer_index < left_effective_integer_len {
323        let left_digit = left_integer_digits[left_integer_start_index + integer_index];
324        let right_digit = right_integer_digits[right_integer_start_index + integer_index];
325        if left_digit < right_digit {
326            return NumberCompareResult::Less;
327        }
328        if left_digit > right_digit {
329            return NumberCompareResult::Greater;
330        }
331        integer_index += 1;
332    }
333
334    let fractional_compare_len = if left_fractional_digits.len() > right_fractional_digits.len() {
335        left_fractional_digits.len()
336    } else {
337        right_fractional_digits.len()
338    };
339    let mut fractional_index = 0;
340    while fractional_index < fractional_compare_len {
341        let left_digit = match left_fractional_digits.get(fractional_index) {
342            Some(digit) => *digit,
343            None => 0,
344        };
345        let right_digit = match right_fractional_digits.get(fractional_index) {
346            Some(digit) => *digit,
347            None => 0,
348        };
349        if left_digit < right_digit {
350            return NumberCompareResult::Less;
351        }
352        if left_digit > right_digit {
353            return NumberCompareResult::Greater;
354        }
355        fractional_index += 1;
356    }
357
358    NumberCompareResult::Equal
359}
360
361pub fn compare_number_strings(
362    left_number_value: &str,
363    right_number_value: &str,
364) -> NumberCompareResult {
365    let (left_is_negative, left_integer_digits, left_fractional_digits) =
366        parse_number_parts_for_comparison(left_number_value);
367    let (right_is_negative, right_integer_digits, right_fractional_digits) =
368        parse_number_parts_for_comparison(right_number_value);
369
370    let left_is_zero =
371        digits_are_all_zero(&left_integer_digits) && digits_are_all_zero(&left_fractional_digits);
372    let right_is_zero =
373        digits_are_all_zero(&right_integer_digits) && digits_are_all_zero(&right_fractional_digits);
374    if left_is_zero && right_is_zero {
375        return NumberCompareResult::Equal;
376    }
377
378    if left_is_negative && !left_is_zero && !right_is_negative {
379        return NumberCompareResult::Less;
380    }
381    if right_is_negative && !right_is_zero && !left_is_negative {
382        return NumberCompareResult::Greater;
383    }
384
385    let non_negative_compare_result = compare_non_negative_decimal_parts(
386        &left_integer_digits,
387        &left_fractional_digits,
388        &right_integer_digits,
389        &right_fractional_digits,
390    );
391    if left_is_negative && !left_is_zero && right_is_negative && !right_is_zero {
392        return match non_negative_compare_result {
393            NumberCompareResult::Less => NumberCompareResult::Greater,
394            NumberCompareResult::Equal => NumberCompareResult::Equal,
395            NumberCompareResult::Greater => NumberCompareResult::Less,
396        };
397    }
398
399    non_negative_compare_result
400}
401
402impl Runtime {
403    /// Sub-goals inside numeric builtins: known env + builtin rules only.
404    /// Do not call [`Runtime::verify_non_equational_atomic_fact`] here: its forall / definition
405    /// round can recurse with outer goals (e.g. `b in R` for `0 <= a^b`, or order lemmas).
406    pub(crate) fn verify_non_equational_known_then_builtin_rules_only(
407        &mut self,
408        atomic_fact: &AtomicFact,
409        verify_state: &VerifyState,
410    ) -> Result<StmtResult, RuntimeError> {
411        let r = self.verify_non_equational_atomic_fact_with_known_atomic_facts(atomic_fact)?;
412        if r.is_true() {
413            return Ok(r);
414        }
415        self.verify_non_equational_atomic_fact_with_builtin_rules(atomic_fact, verify_state)
416    }
417
418    fn verify_zero_order_on_sub_expr(
419        &mut self,
420        zero: &Obj,
421        sub_expr: &Obj,
422        weak: bool,
423        line_file: &LineFile,
424    ) -> Result<StmtResult, RuntimeError> {
425        let fact: AtomicFact = if weak {
426            LessEqualFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
427        } else {
428            LessFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
429        };
430        let mut result = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&fact)?;
431        if !result.is_true() {
432            result = self.verify_order_atomic_fact_numeric_builtin_only(&fact)?;
433        }
434        Ok(result)
435    }
436
437    /// `n >= 0` / `0 <= n` from known `n $in N` (e.g. `forall n N:` domain).
438    fn try_verify_order_nonnegative_from_membership_in_n(
439        &mut self,
440        atomic_fact: &AtomicFact,
441        verify_state: &VerifyState,
442    ) -> Result<Option<StmtResult>, RuntimeError> {
443        let (n, line_file) = match atomic_fact {
444            AtomicFact::GreaterEqualFact(f) => {
445                let Some(z) = self.resolve_obj_to_number(&f.right) else {
446                    return Ok(None);
447                };
448                if !matches!(
449                    compare_normalized_number_str_to_zero(&z.normalized_value),
450                    NumberCompareResult::Equal
451                ) {
452                    return Ok(None);
453                }
454                (f.left.clone(), f.line_file.clone())
455            }
456            AtomicFact::LessEqualFact(f) => {
457                let Some(z) = self.resolve_obj_to_number(&f.left) else {
458                    return Ok(None);
459                };
460                if !matches!(
461                    compare_normalized_number_str_to_zero(&z.normalized_value),
462                    NumberCompareResult::Equal
463                ) {
464                    return Ok(None);
465                }
466                (f.right.clone(), f.line_file.clone())
467            }
468            _ => return Ok(None),
469        };
470        let in_n: AtomicFact = InFact::new(n, StandardSet::N.into(), line_file.clone()).into();
471        if self
472            .verify_non_equational_known_then_builtin_rules_only(&in_n, verify_state)?
473            .is_true()
474        {
475            return Ok(Some(StmtResult::FactualStmtSuccess(
476                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
477                    atomic_fact.clone().into(),
478                    "n >= 0 from n $in N".to_string(),
479                    Vec::new(),
480                ),
481            )));
482        }
483        Ok(None)
484    }
485
486    /// `n >= 1` / `1 <= n` from known `n $in N_pos`.
487    fn try_verify_order_one_le_from_membership_in_n_pos(
488        &mut self,
489        atomic_fact: &AtomicFact,
490        verify_state: &VerifyState,
491    ) -> Result<Option<StmtResult>, RuntimeError> {
492        let (n, line_file) = match atomic_fact {
493            AtomicFact::GreaterEqualFact(f) => {
494                let Some(one) = self.resolve_obj_to_number(&f.right) else {
495                    return Ok(None);
496                };
497                if !matches!(
498                    compare_number_strings(&one.normalized_value, "1"),
499                    NumberCompareResult::Equal
500                ) {
501                    return Ok(None);
502                }
503                (f.left.clone(), f.line_file.clone())
504            }
505            AtomicFact::LessEqualFact(f) => {
506                let Some(one) = self.resolve_obj_to_number(&f.left) else {
507                    return Ok(None);
508                };
509                if !matches!(
510                    compare_number_strings(&one.normalized_value, "1"),
511                    NumberCompareResult::Equal
512                ) {
513                    return Ok(None);
514                }
515                (f.right.clone(), f.line_file.clone())
516            }
517            _ => return Ok(None),
518        };
519        let in_n_pos: AtomicFact =
520            InFact::new(n, StandardSet::NPos.into(), line_file.clone()).into();
521        if self
522            .verify_non_equational_known_then_builtin_rules_only(&in_n_pos, verify_state)?
523            .is_true()
524        {
525            return Ok(Some(StmtResult::FactualStmtSuccess(
526                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
527                    atomic_fact.clone().into(),
528                    "n >= 1 from n $in N_pos".to_string(),
529                    Vec::new(),
530                ),
531            )));
532        }
533        Ok(None)
534    }
535
536    /// `n >= 1` / `1 <= n` from known `n $in N` and `n != 0` (nonzero naturals are at least 1).
537    /// Example: `forall x N: x != 0 =>: 1 <= x`.
538    fn try_verify_order_one_le_from_membership_in_n_and_nonzero(
539        &mut self,
540        atomic_fact: &AtomicFact,
541        verify_state: &VerifyState,
542    ) -> Result<Option<StmtResult>, RuntimeError> {
543        let (n, line_file) = match atomic_fact {
544            AtomicFact::GreaterEqualFact(f) => {
545                let Some(one) = self.resolve_obj_to_number(&f.right) else {
546                    return Ok(None);
547                };
548                if !matches!(
549                    compare_number_strings(&one.normalized_value, "1"),
550                    NumberCompareResult::Equal
551                ) {
552                    return Ok(None);
553                }
554                (f.left.clone(), f.line_file.clone())
555            }
556            AtomicFact::LessEqualFact(f) => {
557                let Some(one) = self.resolve_obj_to_number(&f.left) else {
558                    return Ok(None);
559                };
560                if !matches!(
561                    compare_number_strings(&one.normalized_value, "1"),
562                    NumberCompareResult::Equal
563                ) {
564                    return Ok(None);
565                }
566                (f.right.clone(), f.line_file.clone())
567            }
568            _ => return Ok(None),
569        };
570        let zero_obj: Obj = Number::new("0".to_string()).into();
571        let in_n: AtomicFact =
572            InFact::new(n.clone(), StandardSet::N.into(), line_file.clone()).into();
573        let nonzero: AtomicFact = NotEqualFact::new(n, zero_obj, line_file.clone()).into();
574        if !self
575            .verify_non_equational_known_then_builtin_rules_only(&in_n, verify_state)?
576            .is_true()
577        {
578            return Ok(None);
579        }
580        if !self
581            .verify_non_equational_atomic_fact_with_known_atomic_facts(&nonzero)?
582            .is_true()
583        {
584            return Ok(None);
585        }
586        Ok(Some(StmtResult::FactualStmtSuccess(
587            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
588                atomic_fact.clone().into(),
589                "1 <= n from n $in N and n != 0".to_string(),
590                Vec::new(),
591            ),
592        )))
593    }
594
595    fn verify_zero_le_abs_builtin_rule(
596        &mut self,
597        atomic_fact: &AtomicFact,
598    ) -> Result<Option<StmtResult>, RuntimeError> {
599        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
600            return Ok(None);
601        };
602        let AtomicFact::LessEqualFact(f) = &norm else {
603            return Ok(None);
604        };
605        if f.left.to_string() != "0" {
606            return Ok(None);
607        }
608        if !matches!(&f.right, Obj::Abs(_)) {
609            return Ok(None);
610        }
611        Ok(Some(StmtResult::FactualStmtSuccess(
612            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
613                atomic_fact.clone().into(),
614                "0 <= abs(x) for x in R".to_string(),
615                Vec::new(),
616            ),
617        )))
618    }
619
620    // `(-1)*x` order vs 0: e.g. `x < 0` or `x <= 0` implies `(-1)*x >= 0`; `x > 0` implies `(-1)*x < 0`.
621    // Also handles `0 <= (-1)*x` (equivalently `0 <= -x` when `-x` parses as `(-1)*x`).
622    fn try_verify_order_opposite_sign_mul_minus_one(
623        &mut self,
624        atomic_fact: &AtomicFact,
625        verify_state: &VerifyState,
626    ) -> Result<Option<StmtResult>, RuntimeError> {
627        let z: Obj = Number::new("0".to_string()).into();
628        let success = |msg: &'static str| {
629            Ok(Some(StmtResult::FactualStmtSuccess(
630                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
631                    atomic_fact.clone().into(),
632                    msg.to_string(),
633                    Vec::new(),
634                ),
635            )))
636        };
637        match atomic_fact {
638            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
639                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
640                    let le: AtomicFact =
641                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
642                    if self
643                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
644                        .is_true()
645                    {
646                        return success("order: (-1)*x >= 0 from x <= 0");
647                    }
648                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
649                    if self
650                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
651                        .is_true()
652                    {
653                        return success("order: (-1)*x >= 0 from x < 0");
654                    }
655                }
656                Ok(None)
657            }
658            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.right) => {
659                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
660                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
661                    if self
662                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
663                        .is_true()
664                    {
665                        return success("order: (-1)*x > 0 from x < 0");
666                    }
667                }
668                Ok(None)
669            }
670            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
671                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
672                    let ge: AtomicFact =
673                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
674                    if self
675                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
676                        .is_true()
677                    {
678                        return success("order: (-1)*x <= 0 from x >= 0");
679                    }
680                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
681                    if self
682                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
683                        .is_true()
684                    {
685                        return success("order: (-1)*x <= 0 from x > 0");
686                    }
687                }
688                Ok(None)
689            }
690            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.right) => {
691                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
692                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
693                    if self
694                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
695                        .is_true()
696                    {
697                        return success("order: (-1)*x < 0 from x > 0");
698                    }
699                }
700                Ok(None)
701            }
702            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
703                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
704                    let le: AtomicFact =
705                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
706                    if self
707                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
708                        .is_true()
709                    {
710                        return success("order: 0 <= (-1)*x from x <= 0");
711                    }
712                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
713                    if self
714                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
715                        .is_true()
716                    {
717                        return success("order: 0 <= (-1)*x from x < 0");
718                    }
719                }
720                Ok(None)
721            }
722            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.left) => {
723                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
724                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
725                    if self
726                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
727                        .is_true()
728                    {
729                        return success("order: 0 < (-1)*x from x < 0");
730                    }
731                }
732                Ok(None)
733            }
734            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
735                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
736                    let ge: AtomicFact =
737                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
738                    if self
739                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
740                        .is_true()
741                    {
742                        return success("order: 0 >= (-1)*x from x >= 0");
743                    }
744                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
745                    if self
746                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
747                        .is_true()
748                    {
749                        return success("order: 0 >= (-1)*x from x > 0");
750                    }
751                }
752                Ok(None)
753            }
754            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.left) => {
755                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
756                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
757                    if self
758                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
759                        .is_true()
760                    {
761                        return success("order: 0 > (-1)*x from x > 0");
762                    }
763                }
764                Ok(None)
765            }
766            _ => Ok(None),
767        }
768    }
769
770    // `a > b` from known `not (a <= b)`, `a < b` from `not (a >= b)`, etc. (total order duality).
771    fn verify_order_from_known_negated_complement(
772        &mut self,
773        atomic_fact: &AtomicFact,
774    ) -> Result<Option<StmtResult>, RuntimeError> {
775        let negated: Option<AtomicFact> = match atomic_fact {
776            AtomicFact::GreaterFact(f) => Some(
777                NotLessEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
778            ),
779            AtomicFact::LessFact(f) => Some(
780                NotGreaterEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone())
781                    .into(),
782            ),
783            AtomicFact::GreaterEqualFact(f) => {
784                Some(NotLessFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
785            }
786            AtomicFact::LessEqualFact(f) => Some(
787                NotGreaterFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
788            ),
789            _ => None,
790        };
791        let Some(neg) = negated else {
792            return Ok(None);
793        };
794        let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&neg)?;
795        if sub.is_true() {
796            return Ok(Some(
797                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
798                    atomic_fact.clone().into(),
799                    InferResult::new(),
800                    "order_from_known_negated_complement".to_string(),
801                    vec![sub],
802                )
803                .into(),
804            ));
805        }
806        Ok(None)
807    }
808
809    // `not (a < b)` etc.: only consult known atomic facts for the equivalent weak/strict order.
810    fn verify_negated_order_from_known_equivalent_order(
811        &mut self,
812        atomic_fact: &AtomicFact,
813    ) -> Result<Option<StmtResult>, RuntimeError> {
814        let candidates: Vec<AtomicFact> = match atomic_fact {
815            AtomicFact::NotLessFact(f) => {
816                let lf = f.line_file.clone();
817                vec![
818                    LessEqualFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
819                    GreaterEqualFact::new(f.left.clone(), f.right.clone(), lf).into(),
820                ]
821            }
822            AtomicFact::NotGreaterFact(f) => {
823                let lf = f.line_file.clone();
824                vec![
825                    LessEqualFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
826                    GreaterEqualFact::new(f.right.clone(), f.left.clone(), lf).into(),
827                ]
828            }
829            AtomicFact::NotLessEqualFact(f) => {
830                let lf = f.line_file.clone();
831                vec![
832                    LessFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
833                    GreaterFact::new(f.left.clone(), f.right.clone(), lf).into(),
834                ]
835            }
836            AtomicFact::NotGreaterEqualFact(f) => {
837                let lf = f.line_file.clone();
838                vec![
839                    LessFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
840                    GreaterFact::new(f.right.clone(), f.left.clone(), lf).into(),
841                ]
842            }
843            _ => return Ok(None),
844        };
845        for candidate in candidates {
846            let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&candidate)?;
847            if sub.is_true() {
848                return Ok(Some(
849                    FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
850                        atomic_fact.clone().into(),
851                        InferResult::new(),
852                        "negated_order_from_known_equivalent_order".to_string(),
853                        vec![sub],
854                    )
855                    .into(),
856                ));
857            }
858        }
859        Ok(None)
860    }
861
862    // Matches Lit `a <= b` <=> `0 <= b - a` (and strict): `0 <= u - v` iff `v <= u`, `0 < u - v` iff `v < u`.
863    fn verify_zero_order_on_sub_from_two_sided_order_builtin_rule(
864        &mut self,
865        atomic_fact: &AtomicFact,
866    ) -> Result<Option<StmtResult>, RuntimeError> {
867        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
868            return Ok(None);
869        };
870        match &norm {
871            AtomicFact::LessEqualFact(f) if f.left.to_string() == "0" => {
872                let Obj::Sub(sub) = &f.right else {
873                    return Ok(None);
874                };
875                let derived: AtomicFact = LessEqualFact::new(
876                    sub.right.as_ref().clone(),
877                    sub.left.as_ref().clone(),
878                    f.line_file.clone(),
879                )
880                .into();
881                let mut result =
882                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
883                if !result.is_true() {
884                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
885                }
886                if result.is_true() {
887                    Ok(Some(StmtResult::FactualStmtSuccess(
888                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
889                            atomic_fact.clone().into(),
890                            "0 <= u - v from v <= u".to_string(),
891                            vec![result],
892                        ),
893                    )))
894                } else {
895                    Ok(None)
896                }
897            }
898            AtomicFact::LessFact(f) if f.left.to_string() == "0" => {
899                let Obj::Sub(sub) = &f.right else {
900                    return Ok(None);
901                };
902                let derived: AtomicFact = LessFact::new(
903                    sub.right.as_ref().clone(),
904                    sub.left.as_ref().clone(),
905                    f.line_file.clone(),
906                )
907                .into();
908                let mut result =
909                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
910                if !result.is_true() {
911                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
912                }
913                if result.is_true() {
914                    Ok(Some(StmtResult::FactualStmtSuccess(
915                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
916                            atomic_fact.clone().into(),
917                            "0 < u - v from v < u".to_string(),
918                            vec![result],
919                        ),
920                    )))
921                } else {
922                    Ok(None)
923                }
924            }
925            _ => Ok(None),
926        }
927    }
928
929    fn verify_zero_le_add_from_known_atomic_facts_builtin_rule(
930        &mut self,
931        atomic_fact: &AtomicFact,
932    ) -> Result<Option<StmtResult>, RuntimeError> {
933        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
934            return Ok(None);
935        };
936        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
937            return Ok(None);
938        };
939        if less_equal_fact.left.to_string() != "0" {
940            return Ok(None);
941        }
942        let Obj::Add(add_obj) = &less_equal_fact.right else {
943            return Ok(None);
944        };
945
946        let zero = &less_equal_fact.left;
947        let line_file = &less_equal_fact.line_file;
948        let left_verify_result =
949            self.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
950        if !left_verify_result.is_true() {
951            return Ok(None);
952        }
953        let right_verify_result =
954            self.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
955        if !right_verify_result.is_true() {
956            return Ok(None);
957        }
958
959        Ok(Some(StmtResult::FactualStmtSuccess(
960            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
961                atomic_fact.clone().into(),
962                "0 <= a + b from known atomic facts 0 <= a and 0 <= b".to_string(),
963                vec![left_verify_result, right_verify_result],
964            ),
965        )))
966    }
967
968    fn verify_zero_lt_add_from_known_atomic_facts_builtin_rule(
969        &mut self,
970        atomic_fact: &AtomicFact,
971    ) -> Result<Option<StmtResult>, RuntimeError> {
972        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
973            return Ok(None);
974        };
975        let AtomicFact::LessFact(less_fact) = normalized_fact else {
976            return Ok(None);
977        };
978        if less_fact.left.to_string() != "0" {
979            return Ok(None);
980        }
981        let Obj::Add(add_obj) = &less_fact.right else {
982            return Ok(None);
983        };
984
985        let zero = &less_fact.left;
986        let line_file = &less_fact.line_file;
987
988        let strict_then_weak = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
989            let left_result =
990                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), false, line_file)?;
991            if !left_result.is_true() {
992                return Ok(None);
993            }
994            let right_result =
995                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
996            if !right_result.is_true() {
997                return Ok(None);
998            }
999            Ok(Some(StmtResult::FactualStmtSuccess(
1000                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1001                    atomic_fact.clone().into(),
1002                    "0 < a + b from (0 < a and 0 <= b)".to_string(),
1003                    vec![left_result, right_result],
1004                ),
1005            )))
1006        };
1007        let weak_then_strict = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
1008            let left_result =
1009                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
1010            if !left_result.is_true() {
1011                return Ok(None);
1012            }
1013            let right_result =
1014                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), false, line_file)?;
1015            if !right_result.is_true() {
1016                return Ok(None);
1017            }
1018            Ok(Some(StmtResult::FactualStmtSuccess(
1019                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1020                    atomic_fact.clone().into(),
1021                    "0 < a + b from (0 <= a and 0 < b)".to_string(),
1022                    vec![left_result, right_result],
1023                ),
1024            )))
1025        };
1026
1027        if let Some(success) = strict_then_weak(self)? {
1028            return Ok(Some(success));
1029        }
1030        weak_then_strict(self)
1031    }
1032
1033    fn verify_zero_le_even_integer_pow_builtin_rule(
1034        &mut self,
1035        atomic_fact: &AtomicFact,
1036    ) -> Result<Option<StmtResult>, RuntimeError> {
1037        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1038            return Ok(None);
1039        };
1040        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1041            return Ok(None);
1042        };
1043        if less_equal_fact.left.to_string() != "0" {
1044            return Ok(None);
1045        }
1046        let right = &less_equal_fact.right;
1047        let is_equal_factors_mul = match right {
1048            Obj::Mul(mul_obj) => mul_obj.left.to_string() == mul_obj.right.to_string(),
1049            _ => false,
1050        };
1051        let is_even_pow = match right {
1052            Obj::Pow(pow_obj) => match pow_obj.exponent.as_ref() {
1053                Obj::Number(n) => normalized_decimal_string_is_even_integer(&n.normalized_value),
1054                _ => false,
1055            },
1056            _ => false,
1057        };
1058        if !is_equal_factors_mul && !is_even_pow {
1059            return Ok(None);
1060        }
1061        let msg = if is_equal_factors_mul {
1062            "0 <= a * a from even integer exponent (here 2) (forall a R)".to_string()
1063        } else {
1064            "0 <= a^n for even integer n (forall a R)".to_string()
1065        };
1066        Ok(Some(StmtResult::FactualStmtSuccess(
1067            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1068                atomic_fact.clone().into(),
1069                msg,
1070                Vec::new(),
1071            ),
1072        )))
1073    }
1074
1075    fn verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(
1076        &mut self,
1077        atomic_fact: &AtomicFact,
1078    ) -> Result<Option<StmtResult>, RuntimeError> {
1079        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1080            return Ok(None);
1081        };
1082        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1083            return Ok(None);
1084        };
1085        if less_fact.left.to_string() != "0" {
1086            return Ok(None);
1087        }
1088        let Obj::Pow(pow_obj) = &less_fact.right else {
1089            return Ok(None);
1090        };
1091        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
1092            return Ok(None);
1093        };
1094        if !normalized_decimal_string_is_even_integer(&exp_num.normalized_value) {
1095            return Ok(None);
1096        }
1097
1098        let line_file = less_fact.line_file.clone();
1099        let base = pow_obj.base.as_ref().clone();
1100        let zero_obj: Obj = Number::new("0".to_string()).into();
1101        let base_neq_zero: AtomicFact = NotEqualFact::new(base, zero_obj, line_file.clone()).into();
1102
1103        let neq_result = self.verify_non_equational_known_then_builtin_rules_only(
1104            &base_neq_zero,
1105            &VerifyState::new(0, true),
1106        )?;
1107        if !neq_result.is_true() {
1108            return Ok(None);
1109        }
1110
1111        Ok(Some(StmtResult::FactualStmtSuccess(
1112            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1113                atomic_fact.clone().into(),
1114                "0 < a^n for even integer n from a != 0".to_string(),
1115                vec![neq_result],
1116            ),
1117        )))
1118    }
1119
1120    // Matches `0 < a^b` / `a^b > 0` when `0 < a` is proved (or known) and `b in R`.
1121    fn verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(
1122        &mut self,
1123        atomic_fact: &AtomicFact,
1124    ) -> Result<Option<StmtResult>, RuntimeError> {
1125        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1126            return Ok(None);
1127        };
1128        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1129            return Ok(None);
1130        };
1131        if less_fact.left.to_string() != "0" {
1132            return Ok(None);
1133        }
1134        let Obj::Pow(pow_obj) = &less_fact.right else {
1135            return Ok(None);
1136        };
1137        let zero = &less_fact.left;
1138        let line_file = &less_fact.line_file;
1139        let base = pow_obj.base.as_ref();
1140        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
1141        if !base_result.is_true() {
1142            return Ok(None);
1143        }
1144        let in_r: AtomicFact = InFact::new(
1145            (*pow_obj.exponent).clone(),
1146            StandardSet::R.into(),
1147            line_file.clone(),
1148        )
1149        .into();
1150        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
1151            &in_r,
1152            &VerifyState::new(0, true),
1153        )?;
1154        if !in_r_result.is_true() {
1155            return Ok(None);
1156        }
1157        Ok(Some(StmtResult::FactualStmtSuccess(
1158            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1159                atomic_fact.clone().into(),
1160                "0 < a^b from 0 < a and b in R".to_string(),
1161                vec![base_result, in_r_result],
1162            ),
1163        )))
1164    }
1165
1166    // `0 <= a^b` / `a^b >= 0` with the same premises as strict `0 < a^b`: `0 < a` and `b in R`.
1167    // Covers symbolic exponents (e.g. `2^m`) where the literal-exponent `0 <= a^n` rule does not apply.
1168    fn verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(
1169        &mut self,
1170        atomic_fact: &AtomicFact,
1171    ) -> Result<Option<StmtResult>, RuntimeError> {
1172        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1173            return Ok(None);
1174        };
1175        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1176            return Ok(None);
1177        };
1178        if less_equal_fact.left.to_string() != "0" {
1179            return Ok(None);
1180        }
1181        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1182            return Ok(None);
1183        };
1184        let zero = &less_equal_fact.left;
1185        let line_file = &less_equal_fact.line_file;
1186        let base = pow_obj.base.as_ref();
1187        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
1188        if !base_result.is_true() {
1189            return Ok(None);
1190        }
1191        let in_r: AtomicFact = InFact::new(
1192            (*pow_obj.exponent).clone(),
1193            StandardSet::R.into(),
1194            line_file.clone(),
1195        )
1196        .into();
1197        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
1198            &in_r,
1199            &VerifyState::new(0, true),
1200        )?;
1201        if !in_r_result.is_true() {
1202            return Ok(None);
1203        }
1204        Ok(Some(StmtResult::FactualStmtSuccess(
1205            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1206                atomic_fact.clone().into(),
1207                "0 <= a^b from 0 < a and b in R".to_string(),
1208                vec![base_result, in_r_result],
1209            ),
1210        )))
1211    }
1212
1213    fn verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(
1214        &mut self,
1215        atomic_fact: &AtomicFact,
1216    ) -> Result<Option<StmtResult>, RuntimeError> {
1217        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1218            return Ok(None);
1219        };
1220        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1221            return Ok(None);
1222        };
1223        if less_equal_fact.left.to_string() != "0" {
1224            return Ok(None);
1225        }
1226        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1227            return Ok(None);
1228        };
1229        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
1230            return Ok(None);
1231        };
1232        if !normalized_decimal_string_is_integer(&exp_num.normalized_value) {
1233            return Ok(None);
1234        }
1235
1236        let zero = &less_equal_fact.left;
1237        let line_file = &less_equal_fact.line_file;
1238        let base = pow_obj.base.as_ref();
1239
1240        let exponent_vs_zero = compare_normalized_number_str_to_zero(&exp_num.normalized_value);
1241        let base_result = match exponent_vs_zero {
1242            NumberCompareResult::Less => {
1243                self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?
1244            }
1245            NumberCompareResult::Equal | NumberCompareResult::Greater => {
1246                self.verify_zero_order_on_sub_expr(zero, base, true, line_file)?
1247            }
1248        };
1249        if !base_result.is_true() {
1250            return Ok(None);
1251        }
1252
1253        let msg = match exponent_vs_zero {
1254            NumberCompareResult::Less => "0 <= a^n from 0 < a and integer n < 0".to_string(),
1255            _ => "0 <= a^n from 0 <= a and integer n".to_string(),
1256        };
1257
1258        Ok(Some(StmtResult::FactualStmtSuccess(
1259            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1260                atomic_fact.clone().into(),
1261                msg,
1262                vec![base_result],
1263            ),
1264        )))
1265    }
1266
1267    fn verify_zero_le_mul_from_known_atomic_facts_builtin_rule(
1268        &mut self,
1269        atomic_fact: &AtomicFact,
1270    ) -> Result<Option<StmtResult>, RuntimeError> {
1271        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1272            return Ok(None);
1273        };
1274        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1275            return Ok(None);
1276        };
1277        if less_equal_fact.left.to_string() != "0" {
1278            return Ok(None);
1279        }
1280        let Obj::Mul(mul_obj) = &less_equal_fact.right else {
1281            return Ok(None);
1282        };
1283
1284        let zero = &less_equal_fact.left;
1285        let line_file = &less_equal_fact.line_file;
1286        let left_verify_result =
1287            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), true, line_file)?;
1288        if !left_verify_result.is_true() {
1289            return Ok(None);
1290        }
1291        let right_verify_result =
1292            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), true, line_file)?;
1293        if !right_verify_result.is_true() {
1294            return Ok(None);
1295        }
1296
1297        Ok(Some(StmtResult::FactualStmtSuccess(
1298            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1299                atomic_fact.clone().into(),
1300                "0 <= a * b from 0 <= a and 0 <= b".to_string(),
1301                vec![left_verify_result, right_verify_result],
1302            ),
1303        )))
1304    }
1305
1306    fn verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(
1307        &mut self,
1308        atomic_fact: &AtomicFact,
1309    ) -> Result<Option<StmtResult>, RuntimeError> {
1310        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1311            return Ok(None);
1312        };
1313        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1314            return Ok(None);
1315        };
1316        if less_fact.left.to_string() != "0" {
1317            return Ok(None);
1318        }
1319        let Obj::Mul(mul_obj) = &less_fact.right else {
1320            return Ok(None);
1321        };
1322
1323        let zero = &less_fact.left;
1324        let line_file = &less_fact.line_file;
1325        let left_verify_result =
1326            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), false, line_file)?;
1327        if !left_verify_result.is_true() {
1328            return Ok(None);
1329        }
1330        let right_verify_result =
1331            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), false, line_file)?;
1332        if !right_verify_result.is_true() {
1333            return Ok(None);
1334        }
1335
1336        Ok(Some(StmtResult::FactualStmtSuccess(
1337            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1338                atomic_fact.clone().into(),
1339                "0 < a * b from 0 < a and 0 < b".to_string(),
1340                vec![left_verify_result, right_verify_result],
1341            ),
1342        )))
1343    }
1344
1345    fn verify_zero_le_div_from_known_atomic_facts_builtin_rule(
1346        &mut self,
1347        atomic_fact: &AtomicFact,
1348    ) -> Result<Option<StmtResult>, RuntimeError> {
1349        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1350            return Ok(None);
1351        };
1352        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1353            return Ok(None);
1354        };
1355        if less_equal_fact.left.to_string() != "0" {
1356            return Ok(None);
1357        }
1358        let Obj::Div(div_obj) = &less_equal_fact.right else {
1359            return Ok(None);
1360        };
1361
1362        let zero = &less_equal_fact.left;
1363        let line_file = &less_equal_fact.line_file;
1364        let numer_result =
1365            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), true, line_file)?;
1366        if !numer_result.is_true() {
1367            return Ok(None);
1368        }
1369        let denom_result =
1370            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1371        if !denom_result.is_true() {
1372            return Ok(None);
1373        }
1374
1375        Ok(Some(StmtResult::FactualStmtSuccess(
1376            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1377                atomic_fact.clone().into(),
1378                "0 <= a / b from 0 <= a and 0 < b".to_string(),
1379                vec![numer_result, denom_result],
1380            ),
1381        )))
1382    }
1383
1384    fn verify_zero_lt_div_from_known_atomic_facts_builtin_rule(
1385        &mut self,
1386        atomic_fact: &AtomicFact,
1387    ) -> Result<Option<StmtResult>, RuntimeError> {
1388        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1389            return Ok(None);
1390        };
1391        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1392            return Ok(None);
1393        };
1394        if less_fact.left.to_string() != "0" {
1395            return Ok(None);
1396        }
1397        let Obj::Div(div_obj) = &less_fact.right else {
1398            return Ok(None);
1399        };
1400
1401        let zero = &less_fact.left;
1402        let line_file = &less_fact.line_file;
1403        let numer_result =
1404            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), false, line_file)?;
1405        if !numer_result.is_true() {
1406            return Ok(None);
1407        }
1408        let denom_result =
1409            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1410        if !denom_result.is_true() {
1411            return Ok(None);
1412        }
1413
1414        Ok(Some(StmtResult::FactualStmtSuccess(
1415            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1416                atomic_fact.clone().into(),
1417                "0 < a / b from 0 < a and 0 < b".to_string(),
1418                vec![numer_result, denom_result],
1419            ),
1420        )))
1421    }
1422
1423    fn calculate_obj_pair_to_number_strings(
1424        &self,
1425        left_obj: &Obj,
1426        right_obj: &Obj,
1427    ) -> Option<(String, String)> {
1428        let left_number = self.resolve_obj_to_number_resolved(left_obj)?;
1429        let right_number = self.resolve_obj_to_number_resolved(right_obj)?;
1430        Some((left_number.normalized_value, right_number.normalized_value))
1431    }
1432}