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    /// `n >= 1` / `1 <= n` from known `n $in N` and `n != 0` (nonzero naturals are at least 1).
321    /// Example: `forall x N: x != 0 =>: 1 <= x`.
322    fn try_verify_order_one_le_from_membership_in_n_and_nonzero(
323        &mut self,
324        atomic_fact: &AtomicFact,
325        verify_state: &VerifyState,
326    ) -> Result<Option<StmtResult>, RuntimeError> {
327        let (n, line_file) = match atomic_fact {
328            AtomicFact::GreaterEqualFact(f) => {
329                let Some(one) = self.resolve_obj_to_number(&f.right) else {
330                    return Ok(None);
331                };
332                if !matches!(
333                    compare_number_strings(&one.normalized_value, "1"),
334                    NumberCompareResult::Equal
335                ) {
336                    return Ok(None);
337                }
338                (f.left.clone(), f.line_file.clone())
339            }
340            AtomicFact::LessEqualFact(f) => {
341                let Some(one) = self.resolve_obj_to_number(&f.left) else {
342                    return Ok(None);
343                };
344                if !matches!(
345                    compare_number_strings(&one.normalized_value, "1"),
346                    NumberCompareResult::Equal
347                ) {
348                    return Ok(None);
349                }
350                (f.right.clone(), f.line_file.clone())
351            }
352            _ => return Ok(None),
353        };
354        let zero_obj: Obj = Number::new("0".to_string()).into();
355        let in_n: AtomicFact =
356            InFact::new(n.clone(), StandardSet::N.into(), line_file.clone()).into();
357        let nonzero: AtomicFact = NotEqualFact::new(n, zero_obj, line_file.clone()).into();
358        if !self
359            .verify_non_equational_known_then_builtin_rules_only(&in_n, verify_state)?
360            .is_true()
361        {
362            return Ok(None);
363        }
364        if !self
365            .verify_non_equational_atomic_fact_with_known_atomic_facts(&nonzero)?
366            .is_true()
367        {
368            return Ok(None);
369        }
370        Ok(Some(StmtResult::FactualStmtSuccess(
371            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
372                atomic_fact.clone().into(),
373                "1 <= n from n $in N and n != 0".to_string(),
374                Vec::new(),
375            ),
376        )))
377    }
378
379    fn verify_zero_le_abs_builtin_rule(
380        &mut self,
381        atomic_fact: &AtomicFact,
382    ) -> Result<Option<StmtResult>, RuntimeError> {
383        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
384            return Ok(None);
385        };
386        let AtomicFact::LessEqualFact(f) = &norm else {
387            return Ok(None);
388        };
389        if f.left.to_string() != "0" {
390            return Ok(None);
391        }
392        if !matches!(&f.right, Obj::Abs(_)) {
393            return Ok(None);
394        }
395        Ok(Some(StmtResult::FactualStmtSuccess(
396            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
397                atomic_fact.clone().into(),
398                "0 <= abs(x) for x in R".to_string(),
399                Vec::new(),
400            ),
401        )))
402    }
403
404    // `(-1)*x` order vs 0: e.g. `x < 0` or `x <= 0` implies `(-1)*x >= 0`; `x > 0` implies `(-1)*x < 0`.
405    // Also handles `0 <= (-1)*x` (equivalently `0 <= -x` when `-x` parses as `(-1)*x`).
406    fn try_verify_order_opposite_sign_mul_minus_one(
407        &mut self,
408        atomic_fact: &AtomicFact,
409        verify_state: &VerifyState,
410    ) -> Result<Option<StmtResult>, RuntimeError> {
411        let z: Obj = Number::new("0".to_string()).into();
412        let success = |msg: &'static str| {
413            Ok(Some(StmtResult::FactualStmtSuccess(
414                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
415                    atomic_fact.clone().into(),
416                    msg.to_string(),
417                    Vec::new(),
418                ),
419            )))
420        };
421        match atomic_fact {
422            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
423                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
424                    let le: AtomicFact =
425                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
426                    if self
427                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
428                        .is_true()
429                    {
430                        return success("order: (-1)*x >= 0 from x <= 0");
431                    }
432                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
433                    if self
434                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
435                        .is_true()
436                    {
437                        return success("order: (-1)*x >= 0 from x < 0");
438                    }
439                }
440                Ok(None)
441            }
442            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.right) => {
443                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
444                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
445                    if self
446                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
447                        .is_true()
448                    {
449                        return success("order: (-1)*x > 0 from x < 0");
450                    }
451                }
452                Ok(None)
453            }
454            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
455                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
456                    let ge: AtomicFact =
457                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
458                    if self
459                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
460                        .is_true()
461                    {
462                        return success("order: (-1)*x <= 0 from x >= 0");
463                    }
464                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
465                    if self
466                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
467                        .is_true()
468                    {
469                        return success("order: (-1)*x <= 0 from x > 0");
470                    }
471                }
472                Ok(None)
473            }
474            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.right) => {
475                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
476                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
477                    if self
478                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
479                        .is_true()
480                    {
481                        return success("order: (-1)*x < 0 from x > 0");
482                    }
483                }
484                Ok(None)
485            }
486            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
487                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
488                    let le: AtomicFact =
489                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
490                    if self
491                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
492                        .is_true()
493                    {
494                        return success("order: 0 <= (-1)*x from x <= 0");
495                    }
496                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
497                    if self
498                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
499                        .is_true()
500                    {
501                        return success("order: 0 <= (-1)*x from x < 0");
502                    }
503                }
504                Ok(None)
505            }
506            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.left) => {
507                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
508                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
509                    if self
510                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
511                        .is_true()
512                    {
513                        return success("order: 0 < (-1)*x from x < 0");
514                    }
515                }
516                Ok(None)
517            }
518            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
519                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
520                    let ge: AtomicFact =
521                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
522                    if self
523                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
524                        .is_true()
525                    {
526                        return success("order: 0 >= (-1)*x from x >= 0");
527                    }
528                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
529                    if self
530                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
531                        .is_true()
532                    {
533                        return success("order: 0 >= (-1)*x from x > 0");
534                    }
535                }
536                Ok(None)
537            }
538            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.left) => {
539                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
540                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
541                    if self
542                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
543                        .is_true()
544                    {
545                        return success("order: 0 > (-1)*x from x > 0");
546                    }
547                }
548                Ok(None)
549            }
550            _ => Ok(None),
551        }
552    }
553
554    // Lit `know` facts for the nonnegative / positive cone under field operations used to live in
555    // `BUILTIN_ENV_CODE_FOR_FUNDAMENTAL_COMPARISON` (`fundamental_comparison.rs`). Those fragments
556    // were removed as redundant; the same mathematics is checked here on normalized `0 <=` / `0 <`
557    // goals (possibly after `normalize_positive_order_atomic_fact`):
558    // - Chained `+`: `0 <= a + b + …` from `0 <=` on each peeled summand; `0 < a + b + …` from
559    //   `(0 < a ∧ 0 <= b) ∨ (0 <= a ∧ 0 < b)` at each binary `+`.
560    // - Powers: literal even integer exponent ⇒ `0 <= base^n`; literal integer exponent and `0 <= base`
561    //   (or `0 < base` if exponent < 0) ⇒ `0 <= base^n`; `a * a` with equal factors; `0 < base^exp`
562    //   from `0 < base` and `exp in R`.
563    // - Products and quotients: `0 <= a * b`, `0 < a * b`, `0 <= a / b` (denominator strictly
564    //   positive), `0 < a / b`, each with recursive sub-goals on operands.
565    // The Lit environment still records order via differences (`a <= b` iff `0 <= b - a`, etc.) and
566    // `a != 0 ⇒ 0 < a^2` (strict square). This path also bridges `0 <= u - v` / `0 < u - v` to `v <= u` / `v < u`.
567    // Algebraic closure (+, -, *, /) on general `a <= b` / `a < b` is in `order_algebra_builtin.rs`.
568    pub fn verify_order_atomic_fact_numeric_builtin_only(
569        &mut self,
570        atomic_fact: &AtomicFact,
571    ) -> Result<StmtResult, RuntimeError> {
572        let vs = VerifyState::new(0, true);
573        if let Some(result) =
574            self.try_verify_order_nonnegative_from_membership_in_n(atomic_fact, &vs)?
575        {
576            return Ok(result);
577        }
578        if let Some(result) =
579            self.try_verify_order_one_le_from_membership_in_n_pos(atomic_fact, &vs)?
580        {
581            return Ok(result);
582        }
583        if let Some(result) =
584            self.try_verify_order_one_le_from_membership_in_n_and_nonzero(atomic_fact, &vs)?
585        {
586            return Ok(result);
587        }
588        if let Some(result) = self.try_verify_order_opposite_sign_mul_minus_one(atomic_fact, &vs)? {
589            return Ok(result);
590        }
591        if let Some(result) = self.verify_order_from_known_negated_complement(atomic_fact)? {
592            return Ok(result);
593        }
594        if let Some(result) = self.verify_negated_order_from_known_equivalent_order(atomic_fact)? {
595            return Ok(result);
596        }
597        if let Some(result) = self.verify_order_algebra_structural_builtin_rule(atomic_fact)? {
598            return Ok(result);
599        }
600        if let Some(result) = self.verify_zero_le_abs_builtin_rule(atomic_fact)? {
601            return Ok(result);
602        }
603        if let Some(result) = self.verify_abs_order_builtin_rule(atomic_fact)? {
604            return Ok(result);
605        }
606        if let Some(result) =
607            self.verify_zero_order_on_sub_from_two_sided_order_builtin_rule(atomic_fact)?
608        {
609            return Ok(result);
610        }
611        if let Some(result) =
612            self.verify_zero_le_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
613        {
614            return Ok(result);
615        }
616        if let Some(result) =
617            self.verify_zero_lt_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
618        {
619            return Ok(result);
620        }
621        if let Some(result) = self.verify_zero_le_even_integer_pow_builtin_rule(atomic_fact)? {
622            return Ok(result);
623        }
624        if let Some(result) =
625            self.verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(atomic_fact)?
626        {
627            return Ok(result);
628        }
629        if let Some(result) =
630            self.verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
631        {
632            return Ok(result);
633        }
634        if let Some(result) =
635            self.verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
636        {
637            return Ok(result);
638        }
639        if let Some(result) =
640            self.verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(atomic_fact)?
641        {
642            return Ok(result);
643        }
644        if let Some(result) =
645            self.verify_zero_le_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
646        {
647            return Ok(result);
648        }
649        if let Some(result) =
650            self.verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
651        {
652            return Ok(result);
653        }
654        if let Some(result) =
655            self.verify_zero_le_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
656        {
657            return Ok(result);
658        }
659        if let Some(result) =
660            self.verify_zero_lt_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
661        {
662            return Ok(result);
663        }
664
665        if let AtomicFact::LessEqualFact(less_equal_fact) = atomic_fact {
666            if less_equal_fact.left.to_string() == less_equal_fact.right.to_string() {
667                return Ok(StmtResult::FactualStmtSuccess(
668                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
669                        less_equal_fact.clone().into(),
670                        "less_equal_fact_equal".to_string(),
671                        Vec::new(),
672                    ),
673                ));
674            }
675            let strict_fact: Fact = LessFact::new(
676                less_equal_fact.left.clone(),
677                less_equal_fact.right.clone(),
678                less_equal_fact.line_file.clone(),
679            )
680            .into();
681            let strict_key = strict_fact.to_string();
682            let (cache_ok, _) = self.cache_known_facts_contains(&strict_key);
683            if cache_ok {
684                return Ok(StmtResult::FactualStmtSuccess(
685                    FactualStmtSuccess::new_with_verified_by_known_fact(
686                        less_equal_fact.clone().into(),
687                        VerifiedByResult::Fact(strict_fact, strict_key),
688                        Vec::new(),
689                    ),
690                ));
691            }
692        }
693        if let AtomicFact::GreaterEqualFact(greater_equal_fact) = atomic_fact {
694            if greater_equal_fact.left.to_string() == greater_equal_fact.right.to_string() {
695                return Ok(StmtResult::FactualStmtSuccess(
696                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
697                        greater_equal_fact.clone().into(),
698                        "greater_equal_fact_equal".to_string(),
699                        Vec::new(),
700                    ),
701                ));
702            }
703        }
704        if let Some(true) = self.verify_number_comparison_builtin_rule(atomic_fact) {
705            Ok(StmtResult::FactualStmtSuccess(
706                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
707                    atomic_fact.clone().into(),
708                    "number comparison".to_string(),
709                    Vec::new(),
710                ),
711            ))
712        } else {
713            Ok(StmtResult::StmtUnknown(StmtUnknown::new()))
714        }
715    }
716
717    // `a > b` from known `not (a <= b)`, `a < b` from `not (a >= b)`, etc. (total order duality).
718    fn verify_order_from_known_negated_complement(
719        &mut self,
720        atomic_fact: &AtomicFact,
721    ) -> Result<Option<StmtResult>, RuntimeError> {
722        let negated: Option<AtomicFact> = match atomic_fact {
723            AtomicFact::GreaterFact(f) => Some(
724                NotLessEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
725            ),
726            AtomicFact::LessFact(f) => Some(
727                NotGreaterEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone())
728                    .into(),
729            ),
730            AtomicFact::GreaterEqualFact(f) => {
731                Some(NotLessFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
732            }
733            AtomicFact::LessEqualFact(f) => Some(
734                NotGreaterFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
735            ),
736            _ => None,
737        };
738        let Some(neg) = negated else {
739            return Ok(None);
740        };
741        let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&neg)?;
742        if sub.is_true() {
743            return Ok(Some(
744                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
745                    atomic_fact.clone().into(),
746                    InferResult::new(),
747                    "order_from_known_negated_complement".to_string(),
748                    vec![sub],
749                )
750                .into(),
751            ));
752        }
753        Ok(None)
754    }
755
756    // `not (a < b)` etc.: only consult known atomic facts for the equivalent weak/strict order.
757    fn verify_negated_order_from_known_equivalent_order(
758        &mut self,
759        atomic_fact: &AtomicFact,
760    ) -> Result<Option<StmtResult>, RuntimeError> {
761        let candidates: Vec<AtomicFact> = match atomic_fact {
762            AtomicFact::NotLessFact(f) => {
763                let lf = f.line_file.clone();
764                vec![
765                    LessEqualFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
766                    GreaterEqualFact::new(f.left.clone(), f.right.clone(), lf).into(),
767                ]
768            }
769            AtomicFact::NotGreaterFact(f) => {
770                let lf = f.line_file.clone();
771                vec![
772                    LessEqualFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
773                    GreaterEqualFact::new(f.right.clone(), f.left.clone(), lf).into(),
774                ]
775            }
776            AtomicFact::NotLessEqualFact(f) => {
777                let lf = f.line_file.clone();
778                vec![
779                    LessFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
780                    GreaterFact::new(f.left.clone(), f.right.clone(), lf).into(),
781                ]
782            }
783            AtomicFact::NotGreaterEqualFact(f) => {
784                let lf = f.line_file.clone();
785                vec![
786                    LessFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
787                    GreaterFact::new(f.right.clone(), f.left.clone(), lf).into(),
788                ]
789            }
790            _ => return Ok(None),
791        };
792        for candidate in candidates {
793            let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&candidate)?;
794            if sub.is_true() {
795                return Ok(Some(
796                    FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
797                        atomic_fact.clone().into(),
798                        InferResult::new(),
799                        "negated_order_from_known_equivalent_order".to_string(),
800                        vec![sub],
801                    )
802                    .into(),
803                ));
804            }
805        }
806        Ok(None)
807    }
808
809    // Matches Lit `a <= b` <=> `0 <= b - a` (and strict): `0 <= u - v` iff `v <= u`, `0 < u - v` iff `v < u`.
810    fn verify_zero_order_on_sub_from_two_sided_order_builtin_rule(
811        &mut self,
812        atomic_fact: &AtomicFact,
813    ) -> Result<Option<StmtResult>, RuntimeError> {
814        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
815            return Ok(None);
816        };
817        match &norm {
818            AtomicFact::LessEqualFact(f) if f.left.to_string() == "0" => {
819                let Obj::Sub(sub) = &f.right else {
820                    return Ok(None);
821                };
822                let derived: AtomicFact = LessEqualFact::new(
823                    sub.right.as_ref().clone(),
824                    sub.left.as_ref().clone(),
825                    f.line_file.clone(),
826                )
827                .into();
828                let mut result =
829                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
830                if !result.is_true() {
831                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
832                }
833                if result.is_true() {
834                    Ok(Some(StmtResult::FactualStmtSuccess(
835                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
836                            atomic_fact.clone().into(),
837                            "0 <= u - v from v <= u".to_string(),
838                            vec![result],
839                        ),
840                    )))
841                } else {
842                    Ok(None)
843                }
844            }
845            AtomicFact::LessFact(f) if f.left.to_string() == "0" => {
846                let Obj::Sub(sub) = &f.right else {
847                    return Ok(None);
848                };
849                let derived: AtomicFact = LessFact::new(
850                    sub.right.as_ref().clone(),
851                    sub.left.as_ref().clone(),
852                    f.line_file.clone(),
853                )
854                .into();
855                let mut result =
856                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
857                if !result.is_true() {
858                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
859                }
860                if result.is_true() {
861                    Ok(Some(StmtResult::FactualStmtSuccess(
862                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
863                            atomic_fact.clone().into(),
864                            "0 < u - v from v < u".to_string(),
865                            vec![result],
866                        ),
867                    )))
868                } else {
869                    Ok(None)
870                }
871            }
872            _ => Ok(None),
873        }
874    }
875
876    fn verify_zero_le_add_from_known_atomic_facts_builtin_rule(
877        &mut self,
878        atomic_fact: &AtomicFact,
879    ) -> Result<Option<StmtResult>, RuntimeError> {
880        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
881            return Ok(None);
882        };
883        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
884            return Ok(None);
885        };
886        if less_equal_fact.left.to_string() != "0" {
887            return Ok(None);
888        }
889        let Obj::Add(add_obj) = &less_equal_fact.right else {
890            return Ok(None);
891        };
892
893        let zero = &less_equal_fact.left;
894        let line_file = &less_equal_fact.line_file;
895        let left_verify_result =
896            self.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
897        if !left_verify_result.is_true() {
898            return Ok(None);
899        }
900        let right_verify_result =
901            self.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
902        if !right_verify_result.is_true() {
903            return Ok(None);
904        }
905
906        Ok(Some(StmtResult::FactualStmtSuccess(
907            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
908                atomic_fact.clone().into(),
909                "0 <= a + b from known atomic facts 0 <= a and 0 <= b".to_string(),
910                vec![left_verify_result, right_verify_result],
911            ),
912        )))
913    }
914
915    fn verify_zero_lt_add_from_known_atomic_facts_builtin_rule(
916        &mut self,
917        atomic_fact: &AtomicFact,
918    ) -> Result<Option<StmtResult>, RuntimeError> {
919        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
920            return Ok(None);
921        };
922        let AtomicFact::LessFact(less_fact) = normalized_fact else {
923            return Ok(None);
924        };
925        if less_fact.left.to_string() != "0" {
926            return Ok(None);
927        }
928        let Obj::Add(add_obj) = &less_fact.right else {
929            return Ok(None);
930        };
931
932        let zero = &less_fact.left;
933        let line_file = &less_fact.line_file;
934
935        let strict_then_weak = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
936            let left_result =
937                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), false, line_file)?;
938            if !left_result.is_true() {
939                return Ok(None);
940            }
941            let right_result =
942                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
943            if !right_result.is_true() {
944                return Ok(None);
945            }
946            Ok(Some(StmtResult::FactualStmtSuccess(
947                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
948                    atomic_fact.clone().into(),
949                    "0 < a + b from (0 < a and 0 <= b)".to_string(),
950                    vec![left_result, right_result],
951                ),
952            )))
953        };
954        let weak_then_strict = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
955            let left_result =
956                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
957            if !left_result.is_true() {
958                return Ok(None);
959            }
960            let right_result =
961                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), false, line_file)?;
962            if !right_result.is_true() {
963                return Ok(None);
964            }
965            Ok(Some(StmtResult::FactualStmtSuccess(
966                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
967                    atomic_fact.clone().into(),
968                    "0 < a + b from (0 <= a and 0 < b)".to_string(),
969                    vec![left_result, right_result],
970                ),
971            )))
972        };
973
974        if let Some(success) = strict_then_weak(self)? {
975            return Ok(Some(success));
976        }
977        weak_then_strict(self)
978    }
979
980    fn verify_zero_le_even_integer_pow_builtin_rule(
981        &mut self,
982        atomic_fact: &AtomicFact,
983    ) -> Result<Option<StmtResult>, RuntimeError> {
984        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
985            return Ok(None);
986        };
987        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
988            return Ok(None);
989        };
990        if less_equal_fact.left.to_string() != "0" {
991            return Ok(None);
992        }
993        let right = &less_equal_fact.right;
994        let is_equal_factors_mul = match right {
995            Obj::Mul(mul_obj) => mul_obj.left.to_string() == mul_obj.right.to_string(),
996            _ => false,
997        };
998        let is_even_pow = match right {
999            Obj::Pow(pow_obj) => match pow_obj.exponent.as_ref() {
1000                Obj::Number(n) => normalized_decimal_string_is_even_integer(&n.normalized_value),
1001                _ => false,
1002            },
1003            _ => false,
1004        };
1005        if !is_equal_factors_mul && !is_even_pow {
1006            return Ok(None);
1007        }
1008        let msg = if is_equal_factors_mul {
1009            "0 <= a * a from even integer exponent (here 2) (forall a R)".to_string()
1010        } else {
1011            "0 <= a^n for even integer n (forall a R)".to_string()
1012        };
1013        Ok(Some(StmtResult::FactualStmtSuccess(
1014            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1015                atomic_fact.clone().into(),
1016                msg,
1017                Vec::new(),
1018            ),
1019        )))
1020    }
1021
1022    fn verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(
1023        &mut self,
1024        atomic_fact: &AtomicFact,
1025    ) -> Result<Option<StmtResult>, RuntimeError> {
1026        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1027            return Ok(None);
1028        };
1029        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1030            return Ok(None);
1031        };
1032        if less_fact.left.to_string() != "0" {
1033            return Ok(None);
1034        }
1035        let Obj::Pow(pow_obj) = &less_fact.right else {
1036            return Ok(None);
1037        };
1038        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
1039            return Ok(None);
1040        };
1041        if !normalized_decimal_string_is_even_integer(&exp_num.normalized_value) {
1042            return Ok(None);
1043        }
1044
1045        let line_file = less_fact.line_file.clone();
1046        let base = pow_obj.base.as_ref().clone();
1047        let zero_obj: Obj = Number::new("0".to_string()).into();
1048        let base_neq_zero: AtomicFact = NotEqualFact::new(base, zero_obj, line_file.clone()).into();
1049
1050        let neq_result = self.verify_non_equational_known_then_builtin_rules_only(
1051            &base_neq_zero,
1052            &VerifyState::new(0, true),
1053        )?;
1054        if !neq_result.is_true() {
1055            return Ok(None);
1056        }
1057
1058        Ok(Some(StmtResult::FactualStmtSuccess(
1059            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1060                atomic_fact.clone().into(),
1061                "0 < a^n for even integer n from a != 0".to_string(),
1062                vec![neq_result],
1063            ),
1064        )))
1065    }
1066
1067    // Matches `0 < a^b` / `a^b > 0` when `0 < a` is proved (or known) and `b in R`.
1068    fn verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(
1069        &mut self,
1070        atomic_fact: &AtomicFact,
1071    ) -> Result<Option<StmtResult>, RuntimeError> {
1072        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1073            return Ok(None);
1074        };
1075        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1076            return Ok(None);
1077        };
1078        if less_fact.left.to_string() != "0" {
1079            return Ok(None);
1080        }
1081        let Obj::Pow(pow_obj) = &less_fact.right else {
1082            return Ok(None);
1083        };
1084        let zero = &less_fact.left;
1085        let line_file = &less_fact.line_file;
1086        let base = pow_obj.base.as_ref();
1087        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
1088        if !base_result.is_true() {
1089            return Ok(None);
1090        }
1091        let in_r: AtomicFact = InFact::new(
1092            (*pow_obj.exponent).clone(),
1093            StandardSet::R.into(),
1094            line_file.clone(),
1095        )
1096        .into();
1097        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
1098            &in_r,
1099            &VerifyState::new(0, true),
1100        )?;
1101        if !in_r_result.is_true() {
1102            return Ok(None);
1103        }
1104        Ok(Some(StmtResult::FactualStmtSuccess(
1105            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1106                atomic_fact.clone().into(),
1107                "0 < a^b from 0 < a and b in R".to_string(),
1108                vec![base_result, in_r_result],
1109            ),
1110        )))
1111    }
1112
1113    // `0 <= a^b` / `a^b >= 0` with the same premises as strict `0 < a^b`: `0 < a` and `b in R`.
1114    // Covers symbolic exponents (e.g. `2^m`) where the literal-exponent `0 <= a^n` rule does not apply.
1115    fn verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(
1116        &mut self,
1117        atomic_fact: &AtomicFact,
1118    ) -> Result<Option<StmtResult>, RuntimeError> {
1119        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1120            return Ok(None);
1121        };
1122        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1123            return Ok(None);
1124        };
1125        if less_equal_fact.left.to_string() != "0" {
1126            return Ok(None);
1127        }
1128        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1129            return Ok(None);
1130        };
1131        let zero = &less_equal_fact.left;
1132        let line_file = &less_equal_fact.line_file;
1133        let base = pow_obj.base.as_ref();
1134        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
1135        if !base_result.is_true() {
1136            return Ok(None);
1137        }
1138        let in_r: AtomicFact = InFact::new(
1139            (*pow_obj.exponent).clone(),
1140            StandardSet::R.into(),
1141            line_file.clone(),
1142        )
1143        .into();
1144        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
1145            &in_r,
1146            &VerifyState::new(0, true),
1147        )?;
1148        if !in_r_result.is_true() {
1149            return Ok(None);
1150        }
1151        Ok(Some(StmtResult::FactualStmtSuccess(
1152            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1153                atomic_fact.clone().into(),
1154                "0 <= a^b from 0 < a and b in R".to_string(),
1155                vec![base_result, in_r_result],
1156            ),
1157        )))
1158    }
1159
1160    fn verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(
1161        &mut self,
1162        atomic_fact: &AtomicFact,
1163    ) -> Result<Option<StmtResult>, RuntimeError> {
1164        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1165            return Ok(None);
1166        };
1167        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1168            return Ok(None);
1169        };
1170        if less_equal_fact.left.to_string() != "0" {
1171            return Ok(None);
1172        }
1173        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1174            return Ok(None);
1175        };
1176        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
1177            return Ok(None);
1178        };
1179        if !normalized_decimal_string_is_integer(&exp_num.normalized_value) {
1180            return Ok(None);
1181        }
1182
1183        let zero = &less_equal_fact.left;
1184        let line_file = &less_equal_fact.line_file;
1185        let base = pow_obj.base.as_ref();
1186
1187        let exponent_vs_zero = compare_normalized_number_str_to_zero(&exp_num.normalized_value);
1188        let base_result = match exponent_vs_zero {
1189            NumberCompareResult::Less => {
1190                self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?
1191            }
1192            NumberCompareResult::Equal | NumberCompareResult::Greater => {
1193                self.verify_zero_order_on_sub_expr(zero, base, true, line_file)?
1194            }
1195        };
1196        if !base_result.is_true() {
1197            return Ok(None);
1198        }
1199
1200        let msg = match exponent_vs_zero {
1201            NumberCompareResult::Less => "0 <= a^n from 0 < a and integer n < 0".to_string(),
1202            _ => "0 <= a^n from 0 <= a and integer n".to_string(),
1203        };
1204
1205        Ok(Some(StmtResult::FactualStmtSuccess(
1206            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1207                atomic_fact.clone().into(),
1208                msg,
1209                vec![base_result],
1210            ),
1211        )))
1212    }
1213
1214    fn verify_zero_le_mul_from_known_atomic_facts_builtin_rule(
1215        &mut self,
1216        atomic_fact: &AtomicFact,
1217    ) -> Result<Option<StmtResult>, RuntimeError> {
1218        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1219            return Ok(None);
1220        };
1221        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1222            return Ok(None);
1223        };
1224        if less_equal_fact.left.to_string() != "0" {
1225            return Ok(None);
1226        }
1227        let Obj::Mul(mul_obj) = &less_equal_fact.right else {
1228            return Ok(None);
1229        };
1230
1231        let zero = &less_equal_fact.left;
1232        let line_file = &less_equal_fact.line_file;
1233        let left_verify_result =
1234            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), true, line_file)?;
1235        if !left_verify_result.is_true() {
1236            return Ok(None);
1237        }
1238        let right_verify_result =
1239            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), true, line_file)?;
1240        if !right_verify_result.is_true() {
1241            return Ok(None);
1242        }
1243
1244        Ok(Some(StmtResult::FactualStmtSuccess(
1245            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1246                atomic_fact.clone().into(),
1247                "0 <= a * b from 0 <= a and 0 <= b".to_string(),
1248                vec![left_verify_result, right_verify_result],
1249            ),
1250        )))
1251    }
1252
1253    fn verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(
1254        &mut self,
1255        atomic_fact: &AtomicFact,
1256    ) -> Result<Option<StmtResult>, RuntimeError> {
1257        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1258            return Ok(None);
1259        };
1260        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1261            return Ok(None);
1262        };
1263        if less_fact.left.to_string() != "0" {
1264            return Ok(None);
1265        }
1266        let Obj::Mul(mul_obj) = &less_fact.right else {
1267            return Ok(None);
1268        };
1269
1270        let zero = &less_fact.left;
1271        let line_file = &less_fact.line_file;
1272        let left_verify_result =
1273            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), false, line_file)?;
1274        if !left_verify_result.is_true() {
1275            return Ok(None);
1276        }
1277        let right_verify_result =
1278            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), false, line_file)?;
1279        if !right_verify_result.is_true() {
1280            return Ok(None);
1281        }
1282
1283        Ok(Some(StmtResult::FactualStmtSuccess(
1284            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1285                atomic_fact.clone().into(),
1286                "0 < a * b from 0 < a and 0 < b".to_string(),
1287                vec![left_verify_result, right_verify_result],
1288            ),
1289        )))
1290    }
1291
1292    fn verify_zero_le_div_from_known_atomic_facts_builtin_rule(
1293        &mut self,
1294        atomic_fact: &AtomicFact,
1295    ) -> Result<Option<StmtResult>, RuntimeError> {
1296        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1297            return Ok(None);
1298        };
1299        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1300            return Ok(None);
1301        };
1302        if less_equal_fact.left.to_string() != "0" {
1303            return Ok(None);
1304        }
1305        let Obj::Div(div_obj) = &less_equal_fact.right else {
1306            return Ok(None);
1307        };
1308
1309        let zero = &less_equal_fact.left;
1310        let line_file = &less_equal_fact.line_file;
1311        let numer_result =
1312            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), true, line_file)?;
1313        if !numer_result.is_true() {
1314            return Ok(None);
1315        }
1316        let denom_result =
1317            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1318        if !denom_result.is_true() {
1319            return Ok(None);
1320        }
1321
1322        Ok(Some(StmtResult::FactualStmtSuccess(
1323            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1324                atomic_fact.clone().into(),
1325                "0 <= a / b from 0 <= a and 0 < b".to_string(),
1326                vec![numer_result, denom_result],
1327            ),
1328        )))
1329    }
1330
1331    fn verify_zero_lt_div_from_known_atomic_facts_builtin_rule(
1332        &mut self,
1333        atomic_fact: &AtomicFact,
1334    ) -> Result<Option<StmtResult>, RuntimeError> {
1335        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1336            return Ok(None);
1337        };
1338        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1339            return Ok(None);
1340        };
1341        if less_fact.left.to_string() != "0" {
1342            return Ok(None);
1343        }
1344        let Obj::Div(div_obj) = &less_fact.right else {
1345            return Ok(None);
1346        };
1347
1348        let zero = &less_fact.left;
1349        let line_file = &less_fact.line_file;
1350        let numer_result =
1351            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), false, line_file)?;
1352        if !numer_result.is_true() {
1353            return Ok(None);
1354        }
1355        let denom_result =
1356            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1357        if !denom_result.is_true() {
1358            return Ok(None);
1359        }
1360
1361        Ok(Some(StmtResult::FactualStmtSuccess(
1362            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1363                atomic_fact.clone().into(),
1364                "0 < a / b from 0 < a and 0 < b".to_string(),
1365                vec![numer_result, denom_result],
1366            ),
1367        )))
1368    }
1369
1370    pub fn verify_number_comparison_builtin_rule(&self, atomic_fact: &AtomicFact) -> Option<bool> {
1371        let normalized = normalize_positive_order_atomic_fact(atomic_fact)?;
1372        match normalized {
1373            AtomicFact::LessFact(less_fact) => {
1374                if let Some(calculated_number_string_pair) =
1375                    self.calculate_obj_pair_to_number_strings(&less_fact.left, &less_fact.right)
1376                {
1377                    return Some(matches!(
1378                        compare_number_strings(
1379                            &calculated_number_string_pair.0,
1380                            &calculated_number_string_pair.1
1381                        ),
1382                        NumberCompareResult::Less
1383                    ));
1384                }
1385                self.try_verify_numeric_order_via_div_elimination(
1386                    &less_fact.left,
1387                    &less_fact.right,
1388                    false,
1389                )
1390            }
1391            AtomicFact::LessEqualFact(less_equal_fact) => {
1392                if let Some(calculated_number_string_pair) = self
1393                    .calculate_obj_pair_to_number_strings(
1394                        &less_equal_fact.left,
1395                        &less_equal_fact.right,
1396                    )
1397                {
1398                    let compare_result = compare_number_strings(
1399                        &calculated_number_string_pair.0,
1400                        &calculated_number_string_pair.1,
1401                    );
1402                    return Some(matches!(
1403                        compare_result,
1404                        NumberCompareResult::Less | NumberCompareResult::Equal
1405                    ));
1406                }
1407                self.try_verify_numeric_order_via_div_elimination(
1408                    &less_equal_fact.left,
1409                    &less_equal_fact.right,
1410                    true,
1411                )
1412            }
1413            _ => None,
1414        }
1415    }
1416
1417    fn calculate_obj_pair_to_number_strings(
1418        &self,
1419        left_obj: &Obj,
1420        right_obj: &Obj,
1421    ) -> Option<(String, String)> {
1422        let left_number = self.resolve_obj_to_number_resolved(left_obj)?;
1423        let right_number = self.resolve_obj_to_number_resolved(right_obj)?;
1424        Some((left_number.normalized_value, right_number.normalized_value))
1425    }
1426}