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
69fn 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    fn verify_zero_order_on_sub_expr(
188        &mut self,
189        zero: &Obj,
190        sub_expr: &Obj,
191        weak: bool,
192        line_file: &LineFile,
193    ) -> Result<StmtResult, RuntimeError> {
194        let fact: AtomicFact = if weak {
195            LessEqualFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
196        } else {
197            LessFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
198        };
199        let mut result = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&fact)?;
200        if !result.is_true() {
201            result = self.verify_order_atomic_fact_numeric_builtin_only(&fact)?;
202        }
203        Ok(result)
204    }
205
206    fn verify_zero_le_abs_builtin_rule(
207        &mut self,
208        atomic_fact: &AtomicFact,
209    ) -> Result<Option<StmtResult>, RuntimeError> {
210        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
211            return Ok(None);
212        };
213        let AtomicFact::LessEqualFact(f) = &norm else {
214            return Ok(None);
215        };
216        if f.left.to_string() != "0" {
217            return Ok(None);
218        }
219        if !matches!(&f.right, Obj::Abs(_)) {
220            return Ok(None);
221        }
222        Ok(Some(StmtResult::FactualStmtSuccess(
223            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
224                atomic_fact.clone().into(),
225                "0 <= abs(x) for x in R".to_string(),
226                Vec::new(),
227            ),
228        )))
229    }
230
231    // Lit `know` facts for the nonnegative / positive cone under field operations used to live in
232    // `BUILTIN_ENV_CODE_FOR_FUNDAMENTAL_COMPARISON` (`fundamental_comparison.rs`). Those fragments
233    // were removed as redundant; the same mathematics is checked here on normalized `0 <=` / `0 <`
234    // goals (possibly after `normalize_positive_order_atomic_fact`):
235    // - Chained `+`: `0 <= a + b + …` from `0 <=` on each peeled summand; `0 < a + b + …` from
236    //   `(0 < a ∧ 0 <= b) ∨ (0 <= a ∧ 0 < b)` at each binary `+`.
237    // - Powers: literal even integer exponent ⇒ `0 <= base^n`; literal integer exponent and `0 <= base`
238    //   (or `0 < base` if exponent < 0) ⇒ `0 <= base^n`; `a * a` with equal factors; `0 < base^exp`
239    //   from `0 < base` and `exp in R`.
240    // - Products and quotients: `0 <= a * b`, `0 < a * b`, `0 <= a / b` (denominator strictly
241    //   positive), `0 < a / b`, each with recursive sub-goals on operands.
242    // The Lit environment still records order via differences (`a <= b` iff `0 <= b - a`, etc.) and
243    // `a != 0 ⇒ 0 < a^2` (strict square). This path also bridges `0 <= u - v` / `0 < u - v` to `v <= u` / `v < u`.
244    // Algebraic closure (+, -, *, /) on general `a <= b` / `a < b` is in `order_algebra_builtin.rs`.
245    pub fn verify_order_atomic_fact_numeric_builtin_only(
246        &mut self,
247        atomic_fact: &AtomicFact,
248    ) -> Result<StmtResult, RuntimeError> {
249        if let Some(result) = self.verify_order_from_known_negated_complement(atomic_fact)? {
250            return Ok(result);
251        }
252        if let Some(result) = self.verify_negated_order_from_known_equivalent_order(atomic_fact)? {
253            return Ok(result);
254        }
255        if let Some(result) = self.verify_order_algebra_structural_builtin_rule(atomic_fact)? {
256            return Ok(result);
257        }
258        if let Some(result) = self.verify_zero_le_abs_builtin_rule(atomic_fact)? {
259            return Ok(result);
260        }
261        if let Some(result) =
262            self.verify_zero_order_on_sub_from_two_sided_order_builtin_rule(atomic_fact)?
263        {
264            return Ok(result);
265        }
266        if let Some(result) =
267            self.verify_zero_le_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
268        {
269            return Ok(result);
270        }
271        if let Some(result) =
272            self.verify_zero_lt_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
273        {
274            return Ok(result);
275        }
276        if let Some(result) = self.verify_zero_le_even_integer_pow_builtin_rule(atomic_fact)? {
277            return Ok(result);
278        }
279        if let Some(result) =
280            self.verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(atomic_fact)?
281        {
282            return Ok(result);
283        }
284        if let Some(result) =
285            self.verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
286        {
287            return Ok(result);
288        }
289        if let Some(result) =
290            self.verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(atomic_fact)?
291        {
292            return Ok(result);
293        }
294        if let Some(result) =
295            self.verify_zero_le_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
296        {
297            return Ok(result);
298        }
299        if let Some(result) =
300            self.verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
301        {
302            return Ok(result);
303        }
304        if let Some(result) =
305            self.verify_zero_le_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
306        {
307            return Ok(result);
308        }
309        if let Some(result) =
310            self.verify_zero_lt_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
311        {
312            return Ok(result);
313        }
314
315        if let AtomicFact::LessEqualFact(less_equal_fact) = atomic_fact {
316            if less_equal_fact.left.to_string() == less_equal_fact.right.to_string() {
317                return Ok(StmtResult::FactualStmtSuccess(
318                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
319                        less_equal_fact.clone().into(),
320                        "less_equal_fact_equal".to_string(),
321                        Vec::new(),
322                    ),
323                ));
324            }
325            let strict_fact: Fact = LessFact::new(
326                less_equal_fact.left.clone(),
327                less_equal_fact.right.clone(),
328                less_equal_fact.line_file.clone(),
329            )
330            .into();
331            let strict_key = strict_fact.to_string();
332            let (cache_ok, cache_line_file) = self.cache_known_facts_contains(&strict_key);
333            if cache_ok {
334                return Ok(StmtResult::FactualStmtSuccess(
335                    FactualStmtSuccess::new_with_verified_by_known_fact_source_recording_facts(
336                        less_equal_fact.clone().into(),
337                        strict_key,
338                        Some(strict_fact),
339                        Some(cache_line_file),
340                        Vec::new(),
341                    ),
342                ));
343            }
344        }
345        if let AtomicFact::GreaterEqualFact(greater_equal_fact) = atomic_fact {
346            if greater_equal_fact.left.to_string() == greater_equal_fact.right.to_string() {
347                return Ok(StmtResult::FactualStmtSuccess(
348                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
349                        greater_equal_fact.clone().into(),
350                        "greater_equal_fact_equal".to_string(),
351                        Vec::new(),
352                    ),
353                ));
354            }
355        }
356        if let Some(true) = self.verify_number_comparison_builtin_rule(atomic_fact) {
357            Ok(StmtResult::FactualStmtSuccess(
358                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
359                    atomic_fact.clone().into(),
360                    "number comparison".to_string(),
361                    Vec::new(),
362                ),
363            ))
364        } else {
365            Ok(StmtResult::StmtUnknown(StmtUnknown::new()))
366        }
367    }
368
369    // `a > b` from known `not (a <= b)`, `a < b` from `not (a >= b)`, etc. (total order duality).
370    fn verify_order_from_known_negated_complement(
371        &mut self,
372        atomic_fact: &AtomicFact,
373    ) -> Result<Option<StmtResult>, RuntimeError> {
374        let negated: Option<AtomicFact> = match atomic_fact {
375            AtomicFact::GreaterFact(f) => {
376                Some(NotLessEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
377            }
378            AtomicFact::LessFact(f) => Some(
379                NotGreaterEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
380            ),
381            AtomicFact::GreaterEqualFact(f) => {
382                Some(NotLessFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
383            }
384            AtomicFact::LessEqualFact(f) => {
385                Some(NotGreaterFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
386            }
387            _ => None,
388        };
389        let Some(neg) = negated else {
390            return Ok(None);
391        };
392        let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&neg)?;
393        if sub.is_true() {
394            return Ok(Some(
395                FactualStmtSuccess::new_with_verified_by_builtin_rules(
396                    atomic_fact.clone().into(),
397                    InferResult::new(),
398                    "order_from_known_negated_complement".to_string(),
399                    vec![sub],
400                )
401                .into(),
402            ));
403        }
404        Ok(None)
405    }
406
407    // `not (a < b)` etc.: only consult known atomic facts for the equivalent weak/strict order.
408    fn verify_negated_order_from_known_equivalent_order(
409        &mut self,
410        atomic_fact: &AtomicFact,
411    ) -> Result<Option<StmtResult>, RuntimeError> {
412        let candidates: Vec<AtomicFact> = match atomic_fact {
413            AtomicFact::NotLessFact(f) => {
414                let lf = f.line_file.clone();
415                vec![
416                    LessEqualFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
417                    GreaterEqualFact::new(f.left.clone(), f.right.clone(), lf).into(),
418                ]
419            }
420            AtomicFact::NotGreaterFact(f) => {
421                let lf = f.line_file.clone();
422                vec![
423                    LessEqualFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
424                    GreaterEqualFact::new(f.right.clone(), f.left.clone(), lf).into(),
425                ]
426            }
427            AtomicFact::NotLessEqualFact(f) => {
428                let lf = f.line_file.clone();
429                vec![
430                    LessFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
431                    GreaterFact::new(f.left.clone(), f.right.clone(), lf).into(),
432                ]
433            }
434            AtomicFact::NotGreaterEqualFact(f) => {
435                let lf = f.line_file.clone();
436                vec![
437                    LessFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
438                    GreaterFact::new(f.right.clone(), f.left.clone(), lf).into(),
439                ]
440            }
441            _ => return Ok(None),
442        };
443        for candidate in candidates {
444            let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&candidate)?;
445            if sub.is_true() {
446                return Ok(Some(
447                    FactualStmtSuccess::new_with_verified_by_builtin_rules(
448                        atomic_fact.clone().into(),
449                        InferResult::new(),
450                        "negated_order_from_known_equivalent_order".to_string(),
451                        vec![sub],
452                    )
453                    .into(),
454                ));
455            }
456        }
457        Ok(None)
458    }
459
460    // Matches Lit `a <= b` <=> `0 <= b - a` (and strict): `0 <= u - v` iff `v <= u`, `0 < u - v` iff `v < u`.
461    fn verify_zero_order_on_sub_from_two_sided_order_builtin_rule(
462        &mut self,
463        atomic_fact: &AtomicFact,
464    ) -> Result<Option<StmtResult>, RuntimeError> {
465        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
466            return Ok(None);
467        };
468        match &norm {
469            AtomicFact::LessEqualFact(f) if f.left.to_string() == "0" => {
470                let Obj::Sub(sub) = &f.right else {
471                    return Ok(None);
472                };
473                let derived: AtomicFact = LessEqualFact::new(
474                    sub.right.as_ref().clone(),
475                    sub.left.as_ref().clone(),
476                    f.line_file.clone(),
477                )
478                .into();
479                let mut result =
480                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
481                if !result.is_true() {
482                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
483                }
484                if result.is_true() {
485                    Ok(Some(StmtResult::FactualStmtSuccess(
486                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
487                            atomic_fact.clone().into(),
488                            "0 <= u - v from v <= u".to_string(),
489                            vec![result],
490                        ),
491                    )))
492                } else {
493                    Ok(None)
494                }
495            }
496            AtomicFact::LessFact(f) if f.left.to_string() == "0" => {
497                let Obj::Sub(sub) = &f.right else {
498                    return Ok(None);
499                };
500                let derived: AtomicFact = LessFact::new(
501                    sub.right.as_ref().clone(),
502                    sub.left.as_ref().clone(),
503                    f.line_file.clone(),
504                )
505                .into();
506                let mut result =
507                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
508                if !result.is_true() {
509                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
510                }
511                if result.is_true() {
512                    Ok(Some(StmtResult::FactualStmtSuccess(
513                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
514                            atomic_fact.clone().into(),
515                            "0 < u - v from v < u".to_string(),
516                            vec![result],
517                        ),
518                    )))
519                } else {
520                    Ok(None)
521                }
522            }
523            _ => Ok(None),
524        }
525    }
526
527    fn verify_zero_le_add_from_known_atomic_facts_builtin_rule(
528        &mut self,
529        atomic_fact: &AtomicFact,
530    ) -> Result<Option<StmtResult>, RuntimeError> {
531        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
532            return Ok(None);
533        };
534        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
535            return Ok(None);
536        };
537        if less_equal_fact.left.to_string() != "0" {
538            return Ok(None);
539        }
540        let Obj::Add(add_obj) = &less_equal_fact.right else {
541            return Ok(None);
542        };
543
544        let zero = &less_equal_fact.left;
545        let line_file = &less_equal_fact.line_file;
546        let left_verify_result =
547            self.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
548        if !left_verify_result.is_true() {
549            return Ok(None);
550        }
551        let right_verify_result =
552            self.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
553        if !right_verify_result.is_true() {
554            return Ok(None);
555        }
556
557        Ok(Some(StmtResult::FactualStmtSuccess(
558            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
559                atomic_fact.clone().into(),
560                "0 <= a + b from known atomic facts 0 <= a and 0 <= b".to_string(),
561                vec![left_verify_result, right_verify_result],
562            ),
563        )))
564    }
565
566    fn verify_zero_lt_add_from_known_atomic_facts_builtin_rule(
567        &mut self,
568        atomic_fact: &AtomicFact,
569    ) -> Result<Option<StmtResult>, RuntimeError> {
570        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
571            return Ok(None);
572        };
573        let AtomicFact::LessFact(less_fact) = normalized_fact else {
574            return Ok(None);
575        };
576        if less_fact.left.to_string() != "0" {
577            return Ok(None);
578        }
579        let Obj::Add(add_obj) = &less_fact.right else {
580            return Ok(None);
581        };
582
583        let zero = &less_fact.left;
584        let line_file = &less_fact.line_file;
585
586        let strict_then_weak = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
587            let left_result =
588                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), false, line_file)?;
589            if !left_result.is_true() {
590                return Ok(None);
591            }
592            let right_result =
593                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
594            if !right_result.is_true() {
595                return Ok(None);
596            }
597            Ok(Some(StmtResult::FactualStmtSuccess(
598                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
599                    atomic_fact.clone().into(),
600                    "0 < a + b from (0 < a and 0 <= b)".to_string(),
601                    vec![left_result, right_result],
602                ),
603            )))
604        };
605        let weak_then_strict = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
606            let left_result =
607                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
608            if !left_result.is_true() {
609                return Ok(None);
610            }
611            let right_result =
612                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), false, line_file)?;
613            if !right_result.is_true() {
614                return Ok(None);
615            }
616            Ok(Some(StmtResult::FactualStmtSuccess(
617                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
618                    atomic_fact.clone().into(),
619                    "0 < a + b from (0 <= a and 0 < b)".to_string(),
620                    vec![left_result, right_result],
621                ),
622            )))
623        };
624
625        if let Some(success) = strict_then_weak(self)? {
626            return Ok(Some(success));
627        }
628        weak_then_strict(self)
629    }
630
631    fn verify_zero_le_even_integer_pow_builtin_rule(
632        &mut self,
633        atomic_fact: &AtomicFact,
634    ) -> Result<Option<StmtResult>, RuntimeError> {
635        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
636            return Ok(None);
637        };
638        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
639            return Ok(None);
640        };
641        if less_equal_fact.left.to_string() != "0" {
642            return Ok(None);
643        }
644        let right = &less_equal_fact.right;
645        let is_equal_factors_mul = match right {
646            Obj::Mul(mul_obj) => mul_obj.left.to_string() == mul_obj.right.to_string(),
647            _ => false,
648        };
649        let is_even_pow = match right {
650            Obj::Pow(pow_obj) => match pow_obj.exponent.as_ref() {
651                Obj::Number(n) => normalized_decimal_string_is_even_integer(&n.normalized_value),
652                _ => false,
653            },
654            _ => false,
655        };
656        if !is_equal_factors_mul && !is_even_pow {
657            return Ok(None);
658        }
659        let msg = if is_equal_factors_mul {
660            "0 <= a * a from even integer exponent (here 2) (forall a R)".to_string()
661        } else {
662            "0 <= a^n for even integer n (forall a R)".to_string()
663        };
664        Ok(Some(StmtResult::FactualStmtSuccess(
665            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
666                atomic_fact.clone().into(),
667                msg,
668                Vec::new(),
669            ),
670        )))
671    }
672
673    fn verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(
674        &mut self,
675        atomic_fact: &AtomicFact,
676    ) -> Result<Option<StmtResult>, RuntimeError> {
677        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
678            return Ok(None);
679        };
680        let AtomicFact::LessFact(less_fact) = normalized_fact else {
681            return Ok(None);
682        };
683        if less_fact.left.to_string() != "0" {
684            return Ok(None);
685        }
686        let Obj::Pow(pow_obj) = &less_fact.right else {
687            return Ok(None);
688        };
689        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
690            return Ok(None);
691        };
692        if !normalized_decimal_string_is_even_integer(&exp_num.normalized_value) {
693            return Ok(None);
694        }
695
696        let line_file = less_fact.line_file.clone();
697        let base = pow_obj.base.as_ref().clone();
698        let zero_obj: Obj = Number::new("0".to_string()).into();
699        let base_neq_zero: AtomicFact = NotEqualFact::new(base, zero_obj, line_file.clone()).into();
700
701        let mut neq_result =
702            self.verify_non_equational_atomic_fact_with_known_atomic_facts(&base_neq_zero)?;
703        if !neq_result.is_true() {
704            neq_result = self.verify_non_equational_atomic_fact(
705                &base_neq_zero,
706                &VerifyState::new(0, false),
707                true,
708            )?;
709        }
710        if !neq_result.is_true() {
711            return Ok(None);
712        }
713
714        Ok(Some(StmtResult::FactualStmtSuccess(
715            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
716                atomic_fact.clone().into(),
717                "0 < a^n for even integer n from a != 0".to_string(),
718                vec![neq_result],
719            ),
720        )))
721    }
722
723    // Matches `0 < a^b` / `a^b > 0` when `0 < a` is proved (or known) and `b in R`.
724    fn verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(
725        &mut self,
726        atomic_fact: &AtomicFact,
727    ) -> Result<Option<StmtResult>, RuntimeError> {
728        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
729            return Ok(None);
730        };
731        let AtomicFact::LessFact(less_fact) = normalized_fact else {
732            return Ok(None);
733        };
734        if less_fact.left.to_string() != "0" {
735            return Ok(None);
736        }
737        let Obj::Pow(pow_obj) = &less_fact.right else {
738            return Ok(None);
739        };
740        let zero = &less_fact.left;
741        let line_file = &less_fact.line_file;
742        let base = pow_obj.base.as_ref();
743        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
744        if !base_result.is_true() {
745            return Ok(None);
746        }
747        let in_r: AtomicFact = InFact::new(
748            (*pow_obj.exponent).clone(),
749            StandardSet::R.into(),
750            line_file.clone(),
751        )
752        .into();
753        let mut in_r_result =
754            self.verify_non_equational_atomic_fact_with_known_atomic_facts(&in_r)?;
755        if !in_r_result.is_true() {
756            in_r_result = self.verify_non_equational_atomic_fact(
757                &in_r,
758                &VerifyState::new(0, false),
759                true,
760            )?;
761        }
762        if !in_r_result.is_true() {
763            return Ok(None);
764        }
765        Ok(Some(StmtResult::FactualStmtSuccess(
766            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
767                atomic_fact.clone().into(),
768                "0 < a^b from 0 < a and b in R".to_string(),
769                vec![base_result, in_r_result],
770            ),
771        )))
772    }
773
774    fn verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(
775        &mut self,
776        atomic_fact: &AtomicFact,
777    ) -> Result<Option<StmtResult>, RuntimeError> {
778        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
779            return Ok(None);
780        };
781        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
782            return Ok(None);
783        };
784        if less_equal_fact.left.to_string() != "0" {
785            return Ok(None);
786        }
787        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
788            return Ok(None);
789        };
790        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
791            return Ok(None);
792        };
793        if !normalized_decimal_string_is_integer(&exp_num.normalized_value) {
794            return Ok(None);
795        }
796
797        let zero = &less_equal_fact.left;
798        let line_file = &less_equal_fact.line_file;
799        let base = pow_obj.base.as_ref();
800
801        let exponent_vs_zero = compare_normalized_number_str_to_zero(&exp_num.normalized_value);
802        let base_result = match exponent_vs_zero {
803            NumberCompareResult::Less => {
804                self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?
805            }
806            NumberCompareResult::Equal | NumberCompareResult::Greater => {
807                self.verify_zero_order_on_sub_expr(zero, base, true, line_file)?
808            }
809        };
810        if !base_result.is_true() {
811            return Ok(None);
812        }
813
814        let msg = match exponent_vs_zero {
815            NumberCompareResult::Less => "0 <= a^n from 0 < a and integer n < 0".to_string(),
816            _ => "0 <= a^n from 0 <= a and integer n".to_string(),
817        };
818
819        Ok(Some(StmtResult::FactualStmtSuccess(
820            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
821                atomic_fact.clone().into(),
822                msg,
823                vec![base_result],
824            ),
825        )))
826    }
827
828    fn verify_zero_le_mul_from_known_atomic_facts_builtin_rule(
829        &mut self,
830        atomic_fact: &AtomicFact,
831    ) -> Result<Option<StmtResult>, RuntimeError> {
832        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
833            return Ok(None);
834        };
835        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
836            return Ok(None);
837        };
838        if less_equal_fact.left.to_string() != "0" {
839            return Ok(None);
840        }
841        let Obj::Mul(mul_obj) = &less_equal_fact.right else {
842            return Ok(None);
843        };
844
845        let zero = &less_equal_fact.left;
846        let line_file = &less_equal_fact.line_file;
847        let left_verify_result =
848            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), true, line_file)?;
849        if !left_verify_result.is_true() {
850            return Ok(None);
851        }
852        let right_verify_result =
853            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), true, line_file)?;
854        if !right_verify_result.is_true() {
855            return Ok(None);
856        }
857
858        Ok(Some(StmtResult::FactualStmtSuccess(
859            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
860                atomic_fact.clone().into(),
861                "0 <= a * b from 0 <= a and 0 <= b".to_string(),
862                vec![left_verify_result, right_verify_result],
863            ),
864        )))
865    }
866
867    fn verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(
868        &mut self,
869        atomic_fact: &AtomicFact,
870    ) -> Result<Option<StmtResult>, RuntimeError> {
871        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
872            return Ok(None);
873        };
874        let AtomicFact::LessFact(less_fact) = normalized_fact else {
875            return Ok(None);
876        };
877        if less_fact.left.to_string() != "0" {
878            return Ok(None);
879        }
880        let Obj::Mul(mul_obj) = &less_fact.right else {
881            return Ok(None);
882        };
883
884        let zero = &less_fact.left;
885        let line_file = &less_fact.line_file;
886        let left_verify_result =
887            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), false, line_file)?;
888        if !left_verify_result.is_true() {
889            return Ok(None);
890        }
891        let right_verify_result =
892            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), false, line_file)?;
893        if !right_verify_result.is_true() {
894            return Ok(None);
895        }
896
897        Ok(Some(StmtResult::FactualStmtSuccess(
898            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
899                atomic_fact.clone().into(),
900                "0 < a * b from 0 < a and 0 < b".to_string(),
901                vec![left_verify_result, right_verify_result],
902            ),
903        )))
904    }
905
906    fn verify_zero_le_div_from_known_atomic_facts_builtin_rule(
907        &mut self,
908        atomic_fact: &AtomicFact,
909    ) -> Result<Option<StmtResult>, RuntimeError> {
910        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
911            return Ok(None);
912        };
913        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
914            return Ok(None);
915        };
916        if less_equal_fact.left.to_string() != "0" {
917            return Ok(None);
918        }
919        let Obj::Div(div_obj) = &less_equal_fact.right else {
920            return Ok(None);
921        };
922
923        let zero = &less_equal_fact.left;
924        let line_file = &less_equal_fact.line_file;
925        let numer_result =
926            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), true, line_file)?;
927        if !numer_result.is_true() {
928            return Ok(None);
929        }
930        let denom_result =
931            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
932        if !denom_result.is_true() {
933            return Ok(None);
934        }
935
936        Ok(Some(StmtResult::FactualStmtSuccess(
937            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
938                atomic_fact.clone().into(),
939                "0 <= a / b from 0 <= a and 0 < b".to_string(),
940                vec![numer_result, denom_result],
941            ),
942        )))
943    }
944
945    fn verify_zero_lt_div_from_known_atomic_facts_builtin_rule(
946        &mut self,
947        atomic_fact: &AtomicFact,
948    ) -> Result<Option<StmtResult>, RuntimeError> {
949        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
950            return Ok(None);
951        };
952        let AtomicFact::LessFact(less_fact) = normalized_fact else {
953            return Ok(None);
954        };
955        if less_fact.left.to_string() != "0" {
956            return Ok(None);
957        }
958        let Obj::Div(div_obj) = &less_fact.right else {
959            return Ok(None);
960        };
961
962        let zero = &less_fact.left;
963        let line_file = &less_fact.line_file;
964        let numer_result =
965            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), false, line_file)?;
966        if !numer_result.is_true() {
967            return Ok(None);
968        }
969        let denom_result =
970            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
971        if !denom_result.is_true() {
972            return Ok(None);
973        }
974
975        Ok(Some(StmtResult::FactualStmtSuccess(
976            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
977                atomic_fact.clone().into(),
978                "0 < a / b from 0 < a and 0 < b".to_string(),
979                vec![numer_result, denom_result],
980            ),
981        )))
982    }
983
984    pub fn verify_number_comparison_builtin_rule(&self, atomic_fact: &AtomicFact) -> Option<bool> {
985        let normalized = normalize_positive_order_atomic_fact(atomic_fact)?;
986        match normalized {
987            AtomicFact::LessFact(less_fact) => {
988                if let Some(calculated_number_string_pair) =
989                    self.calculate_obj_pair_to_number_strings(&less_fact.left, &less_fact.right)
990                {
991                    return Some(matches!(
992                        compare_number_strings(
993                            &calculated_number_string_pair.0,
994                            &calculated_number_string_pair.1
995                        ),
996                        NumberCompareResult::Less
997                    ));
998                }
999                self.try_verify_numeric_order_via_div_elimination(
1000                    &less_fact.left,
1001                    &less_fact.right,
1002                    false,
1003                )
1004            }
1005            AtomicFact::LessEqualFact(less_equal_fact) => {
1006                if let Some(calculated_number_string_pair) = self
1007                    .calculate_obj_pair_to_number_strings(
1008                        &less_equal_fact.left,
1009                        &less_equal_fact.right,
1010                    )
1011                {
1012                    let compare_result = compare_number_strings(
1013                        &calculated_number_string_pair.0,
1014                        &calculated_number_string_pair.1,
1015                    );
1016                    return Some(matches!(
1017                        compare_result,
1018                        NumberCompareResult::Less | NumberCompareResult::Equal
1019                    ));
1020                }
1021                self.try_verify_numeric_order_via_div_elimination(
1022                    &less_equal_fact.left,
1023                    &less_equal_fact.right,
1024                    true,
1025                )
1026            }
1027            _ => None,
1028        }
1029    }
1030
1031    fn calculate_obj_pair_to_number_strings(
1032        &self,
1033        left_obj: &Obj,
1034        right_obj: &Obj,
1035    ) -> Option<(String, String)> {
1036        let left_number = self.resolve_obj_to_number_resolved(left_obj)?;
1037        let right_number = self.resolve_obj_to_number_resolved(right_obj)?;
1038        Some((left_number.normalized_value, right_number.normalized_value))
1039    }
1040}