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