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