Skip to main content

litex/verify/verify_builtin_rules/
number_compare.rs

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