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