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