Skip to main content

litex/verify/verify_builtin_rules/
not_equal_builtin.rs

1use crate::prelude::*;
2use crate::verify::verify_number_in_standard_set::is_integer_after_simplification;
3
4impl Runtime {
5    pub fn _verify_not_equal_fact_with_builtin_rules(
6        &mut self,
7        not_equal_fact: &NotEqualFact,
8        verify_state: &VerifyState,
9    ) -> Result<StmtResult, RuntimeError> {
10        let left_obj = &not_equal_fact.left;
11        let right_obj = &not_equal_fact.right;
12
13        match (
14            self.resolve_obj_to_number_for_not_equal_builtin_rule(left_obj),
15            self.resolve_obj_to_number_for_not_equal_builtin_rule(right_obj),
16        ) {
17            (Some(left_number), Some(right_number)) => {
18                if left_number.normalized_value != right_number.normalized_value {
19                    return Ok(
20                        (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
21                            not_equal_fact.clone().into(),
22                            "not_equal_numeric_resolved_or_equal_class_calculation".to_string(),
23                            Vec::new(),
24                        ))
25                        .into(),
26                    );
27                }
28            }
29            _ => {}
30        }
31
32        if let (Obj::ListSet(left_ls), Obj::ListSet(right_ls)) = (left_obj, right_obj) {
33            if left_ls.list.len() != right_ls.list.len() {
34                return Ok(
35                    (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
36                        not_equal_fact.clone().into(),
37                        "list_set_different_length".to_string(),
38                        Vec::new(),
39                    ))
40                    .into(),
41                );
42            }
43        }
44
45        if let Some(verified_result) =
46            self.try_verify_not_equal_from_known_strict_order(not_equal_fact)?
47        {
48            return Ok(verified_result);
49        }
50
51        if let Some(verified_result) =
52            self.try_verify_not_equal_zero_from_n_and_one_le(not_equal_fact, verify_state)?
53        {
54            return Ok(verified_result);
55        }
56
57        if let Some(verified_result) =
58            self.try_verify_not_equal_pow_from_base_nonzero(not_equal_fact, verify_state)?
59        {
60            return Ok(verified_result);
61        }
62
63        if let Some(verified_result) = self
64            .try_verify_square_sum_not_equal_zero_from_nonzero_component(
65                not_equal_fact,
66                verify_state,
67            )?
68        {
69            return Ok(verified_result);
70        }
71
72        match self
73            .try_verify_not_equal_fact_when_zero_and_binary_arithmetic_reduces_by_operand_facts(
74                not_equal_fact,
75                verify_state,
76            )? {
77            Some(verified_result) => return Ok(verified_result),
78            None => {}
79        }
80
81        Ok((StmtUnknown::new()).into())
82    }
83}
84
85impl Runtime {
86    fn try_parse_number_literal_obj_string_for_not_equal_builtin_rule(
87        &self,
88        obj_string: &str,
89    ) -> Option<Number> {
90        let trimmed = obj_string.trim();
91        if trimmed.is_empty() {
92            return None;
93        }
94        let parsed = Number::new(trimmed.to_string());
95        if parsed.to_string() == trimmed {
96            return Some(parsed);
97        }
98        None
99    }
100
101    fn resolve_obj_to_number_for_not_equal_builtin_rule(&self, obj: &Obj) -> Option<Number> {
102        if let Some(number) = self.resolve_obj_to_number_resolved(obj) {
103            return Some(number);
104        }
105        let obj_key = obj.to_string();
106        if let Some(number) = self.get_object_equal_to_normalized_decimal_number(&obj_key) {
107            return Some(number);
108        }
109        let all_equal_obj_strings = self.get_all_objs_equal_to_given(&obj_key);
110        for equal_obj_string in all_equal_obj_strings {
111            if let Some(number) =
112                self.get_object_equal_to_normalized_decimal_number(&equal_obj_string)
113            {
114                return Some(number);
115            }
116            if let Some(number) = self
117                .try_parse_number_literal_obj_string_for_not_equal_builtin_rule(&equal_obj_string)
118            {
119                return Some(number);
120            }
121        }
122        None
123    }
124
125    // x < y or x > y (including y < x / y > x spellings) in known facts implies x != y.
126    fn try_verify_not_equal_from_known_strict_order(
127        &mut self,
128        not_equal_fact: &NotEqualFact,
129    ) -> Result<Option<StmtResult>, RuntimeError> {
130        let line_file = not_equal_fact.line_file.clone();
131        let x = not_equal_fact.left.clone();
132        let y = not_equal_fact.right.clone();
133        let candidates: [AtomicFact; 4] = [
134            LessFact::new(x.clone(), y.clone(), line_file.clone()).into(),
135            GreaterFact::new(x.clone(), y.clone(), line_file.clone()).into(),
136            LessFact::new(y.clone(), x.clone(), line_file.clone()).into(),
137            GreaterFact::new(y.clone(), x.clone(), line_file.clone()).into(),
138        ];
139        for order_atomic in candidates {
140            let sub =
141                self.verify_non_equational_atomic_fact_with_known_atomic_facts(&order_atomic)?;
142            if sub.is_true() {
143                return Ok(Some(
144                    FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
145                        not_equal_fact.clone().into(),
146                        InferResult::new(),
147                        "not_equal_from_known_strict_order".to_string(),
148                        vec![sub],
149                    )
150                    .into(),
151                ));
152            }
153        }
154        Ok(None)
155    }
156
157    /// `n != 0` from `n $in N` and `1 <= n` (or `n >= 1`). Example: `forall x N: 1 <= x =>: x != 0`.
158    fn try_verify_not_equal_zero_from_n_and_one_le(
159        &mut self,
160        not_equal_fact: &NotEqualFact,
161        verify_state: &VerifyState,
162    ) -> Result<Option<StmtResult>, RuntimeError> {
163        let line_file = not_equal_fact.line_file.clone();
164        let one_obj: Obj = Number::new("1".to_string()).into();
165        let x = match (&not_equal_fact.left, &not_equal_fact.right) {
166            (l, r) if self.obj_represents_zero_for_not_equal_builtin_rules(r) => l.clone(),
167            (l, r) if self.obj_represents_zero_for_not_equal_builtin_rules(l) => r.clone(),
168            _ => return Ok(None),
169        };
170        let in_n: AtomicFact =
171            InFact::new(x.clone(), StandardSet::N.into(), line_file.clone()).into();
172        if !self
173            .verify_non_equational_known_then_builtin_rules_only(&in_n, verify_state)?
174            .is_true()
175        {
176            return Ok(None);
177        }
178        let ge: AtomicFact =
179            GreaterEqualFact::new(x.clone(), one_obj.clone(), line_file.clone()).into();
180        if self
181            .verify_non_equational_atomic_fact_with_known_atomic_facts(&ge)?
182            .is_true()
183        {
184            return Ok(Some(
185                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
186                    not_equal_fact.clone().into(),
187                    "n != 0 from n $in N and 1 <= n".to_string(),
188                    Vec::new(),
189                )
190                .into(),
191            ));
192        }
193        let one_le: AtomicFact = LessEqualFact::new(one_obj, x, line_file.clone()).into();
194        if self
195            .verify_non_equational_atomic_fact_with_known_atomic_facts(&one_le)?
196            .is_true()
197        {
198            return Ok(Some(
199                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
200                    not_equal_fact.clone().into(),
201                    "n != 0 from n $in N and 1 <= n".to_string(),
202                    Vec::new(),
203                )
204                .into(),
205            ));
206        }
207        Ok(None)
208    }
209
210    // a^n != 0 with literal integer exponent n, from a != 0 (known / full non-equational verify).
211    fn try_verify_not_equal_pow_from_base_nonzero(
212        &mut self,
213        not_equal_fact: &NotEqualFact,
214        verify_state: &VerifyState,
215    ) -> Result<Option<StmtResult>, RuntimeError> {
216        let line_file = not_equal_fact.line_file.clone();
217        let zero_obj: Obj = Number::new("0".to_string()).into();
218        let pow = match (&not_equal_fact.left, &not_equal_fact.right) {
219            (Obj::Pow(p), r) if self.obj_represents_zero_for_not_equal_builtin_rules(r) => p,
220            (l, Obj::Pow(p)) if self.obj_represents_zero_for_not_equal_builtin_rules(l) => p,
221            _ => return Ok(None),
222        };
223        let Obj::Number(exp_num) = pow.exponent.as_ref() else {
224            return Ok(None);
225        };
226        if !is_integer_after_simplification(exp_num) {
227            return Ok(None);
228        }
229
230        let base = pow.base.as_ref().clone();
231        let base_neq_zero: AtomicFact = NotEqualFact::new(base, zero_obj, line_file.clone()).into();
232        let mut result =
233            self.verify_non_equational_atomic_fact_with_known_atomic_facts(&base_neq_zero)?;
234        if !result.is_true() {
235            result = self.verify_non_equational_atomic_fact(&base_neq_zero, verify_state, true)?;
236        }
237        if result.is_true() {
238            return Ok(Some(
239                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
240                    not_equal_fact.clone().into(),
241                    InferResult::new(),
242                    "not_equal_pow_from_base_nonzero".to_string(),
243                    vec![result],
244                )
245                .into(),
246            ));
247        }
248        Ok(None)
249    }
250
251    // If `a != 0 or b != 0` is known, then `a^2 + b^2 != 0`.
252    // This also accepts the expanded square spelling `a*a + b*b`.
253    // Example:
254    // `forall x, y R: x != 0 or y != 0 <=>: x^2 + y^2 != 0`.
255    fn try_verify_square_sum_not_equal_zero_from_nonzero_component(
256        &mut self,
257        not_equal_fact: &NotEqualFact,
258        verify_state: &VerifyState,
259    ) -> Result<Option<StmtResult>, RuntimeError> {
260        let line_file = not_equal_fact.line_file.clone();
261        let expression_obj =
262            if self.obj_represents_zero_for_not_equal_builtin_rules(&not_equal_fact.right) {
263                &not_equal_fact.left
264            } else if self.obj_represents_zero_for_not_equal_builtin_rules(&not_equal_fact.left) {
265                &not_equal_fact.right
266            } else {
267                return Ok(None);
268            };
269
270        let Some((left_base, right_base)) =
271            self.square_sum_bases_for_not_equal_zero(expression_obj)
272        else {
273            return Ok(None);
274        };
275
276        let zero_obj: Obj = Number::new("0".to_string()).into();
277        let left_nonzero: AtomicFact =
278            NotEqualFact::new(left_base.clone(), zero_obj.clone(), line_file.clone()).into();
279        let right_nonzero: AtomicFact =
280            NotEqualFact::new(right_base.clone(), zero_obj, line_file.clone()).into();
281        let known_or = OrFact::new(
282            vec![
283                AndChainAtomicFact::AtomicFact(left_nonzero.clone()),
284                AndChainAtomicFact::AtomicFact(right_nonzero.clone()),
285            ],
286            line_file.clone(),
287        );
288
289        let mut steps = Vec::new();
290        let known_or_result = self.verify_or_fact(&known_or, verify_state)?;
291        if known_or_result.is_true() {
292            steps.push(known_or_result);
293            return Ok(Some(
294                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
295                    not_equal_fact.clone().into(),
296                    InferResult::new(),
297                    "square_sum_not_equal_zero_from_nonzero_component_or".to_string(),
298                    steps,
299                )
300                .into(),
301            ));
302        }
303
304        let left_result =
305            self.verify_non_equational_atomic_fact(&left_nonzero, verify_state, true)?;
306        if left_result.is_true() {
307            steps.push(left_result);
308            return Ok(Some(
309                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
310                    not_equal_fact.clone().into(),
311                    InferResult::new(),
312                    "square_sum_not_equal_zero_from_left_nonzero".to_string(),
313                    steps,
314                )
315                .into(),
316            ));
317        }
318
319        let right_result =
320            self.verify_non_equational_atomic_fact(&right_nonzero, verify_state, true)?;
321        if right_result.is_true() {
322            steps.push(right_result);
323            return Ok(Some(
324                FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
325                    not_equal_fact.clone().into(),
326                    InferResult::new(),
327                    "square_sum_not_equal_zero_from_right_nonzero".to_string(),
328                    steps,
329                )
330                .into(),
331            ));
332        }
333
334        Ok(None)
335    }
336
337    fn square_sum_bases_for_not_equal_zero(&self, obj: &Obj) -> Option<(Obj, Obj)> {
338        let Obj::Add(add) = obj else {
339            return None;
340        };
341        let left_base = self.square_base_for_not_equal_zero(add.left.as_ref())?;
342        let right_base = self.square_base_for_not_equal_zero(add.right.as_ref())?;
343        Some((left_base, right_base))
344    }
345
346    fn square_base_for_not_equal_zero(&self, obj: &Obj) -> Option<Obj> {
347        match obj {
348            Obj::Pow(pow) => {
349                let Obj::Number(exp_number) = pow.exponent.as_ref() else {
350                    return None;
351                };
352                if exp_number.to_string() == "2" {
353                    Some(pow.base.as_ref().clone())
354                } else {
355                    None
356                }
357            }
358            Obj::Mul(mul) if mul.left.as_ref().to_string() == mul.right.as_ref().to_string() => {
359                Some(mul.left.as_ref().clone())
360            }
361            _ => None,
362        }
363    }
364
365    fn obj_represents_zero_for_not_equal_builtin_rules(self: &Self, obj: &Obj) -> bool {
366        match self.resolve_obj_to_number(obj) {
367            Some(number) => number.normalized_value == "0",
368            None => false,
369        }
370    }
371
372    fn operand_is_not_equal_to_zero_by_known_non_equational_facts(
373        &mut self,
374        operand: &Obj,
375        line_file: LineFile,
376    ) -> Result<bool, RuntimeError> {
377        let zero_obj: Obj = Number::new("0".to_string()).into();
378        let operand_not_equal_zero_fact =
379            NotEqualFact::new(operand.clone(), zero_obj, line_file).into();
380        let verify_result = self.verify_non_equational_atomic_fact_with_known_atomic_facts(
381            &operand_not_equal_zero_fact,
382        )?;
383        Ok(verify_result.is_true())
384    }
385
386    fn both_operands_nonzero_by_known_non_equational_facts(
387        &mut self,
388        left_operand: &Obj,
389        right_operand: &Obj,
390        line_file: LineFile,
391    ) -> Result<bool, RuntimeError> {
392        let left_nonzero = self.operand_is_not_equal_to_zero_by_known_non_equational_facts(
393            left_operand,
394            line_file.clone(),
395        )?;
396        if !left_nonzero {
397            return Ok(false);
398        }
399        self.operand_is_not_equal_to_zero_by_known_non_equational_facts(right_operand, line_file)
400    }
401
402    fn both_operands_strictly_positive_by_non_equational_verify(
403        &mut self,
404        left_operand: &Obj,
405        right_operand: &Obj,
406        line_file: LineFile,
407        verify_state: &VerifyState,
408    ) -> Result<bool, RuntimeError> {
409        let zero_obj: Obj = Number::new("0".to_string()).into();
410        let zero_less_than_left =
411            LessFact::new(zero_obj.clone(), left_operand.clone(), line_file.clone()).into();
412        if !self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
413            &zero_less_than_left,
414            verify_state,
415        )? {
416            return Ok(false);
417        }
418        let zero_less_than_right = LessFact::new(zero_obj, right_operand.clone(), line_file).into();
419        self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
420            &zero_less_than_right,
421            verify_state,
422        )
423    }
424
425    fn both_operands_strictly_negative_by_non_equational_verify(
426        &mut self,
427        left_operand: &Obj,
428        right_operand: &Obj,
429        line_file: LineFile,
430        verify_state: &VerifyState,
431    ) -> Result<bool, RuntimeError> {
432        let zero_obj: Obj = Number::new("0".to_string()).into();
433        let left_less_than_zero =
434            LessFact::new(left_operand.clone(), zero_obj.clone(), line_file.clone()).into();
435        if !self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
436            &left_less_than_zero,
437            verify_state,
438        )? {
439            return Ok(false);
440        }
441        let right_less_than_zero = LessFact::new(right_operand.clone(), zero_obj, line_file).into();
442        self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
443            &right_less_than_zero,
444            verify_state,
445        )
446    }
447
448    pub fn mul_product_negative_when_factors_have_strict_opposite_sign_by_non_equational_verify(
449        &mut self,
450        left_factor: &Obj,
451        right_factor: &Obj,
452        line_file: LineFile,
453        verify_state: &VerifyState,
454    ) -> Result<bool, RuntimeError> {
455        let zero_obj: Obj = Number::new("0".to_string()).into();
456        let left_less_than_zero =
457            LessFact::new(left_factor.clone(), zero_obj.clone(), line_file.clone()).into();
458        let zero_less_than_right =
459            LessFact::new(zero_obj.clone(), right_factor.clone(), line_file.clone()).into();
460        if self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
461            &left_less_than_zero,
462            verify_state,
463        )? && self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
464            &zero_less_than_right,
465            verify_state,
466        )? {
467            return Ok(true);
468        }
469        let zero_less_than_left =
470            LessFact::new(zero_obj.clone(), left_factor.clone(), line_file.clone()).into();
471        let right_less_than_zero = LessFact::new(right_factor.clone(), zero_obj, line_file).into();
472        Ok(
473            self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
474                &zero_less_than_left,
475                verify_state,
476            )? && self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
477                &right_less_than_zero,
478                verify_state,
479            )?,
480        )
481    }
482
483    fn sub_difference_nonzero_when_operands_have_strict_opposite_sign_by_non_equational_verify(
484        &mut self,
485        minuend: &Obj,
486        subtrahend: &Obj,
487        line_file: LineFile,
488        verify_state: &VerifyState,
489    ) -> Result<bool, RuntimeError> {
490        let zero_obj: Obj = Number::new("0".to_string()).into();
491        let zero_less_than_minuend =
492            LessFact::new(zero_obj.clone(), minuend.clone(), line_file.clone()).into();
493        let subtrahend_less_than_zero =
494            LessFact::new(subtrahend.clone(), zero_obj.clone(), line_file.clone()).into();
495        if self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
496            &zero_less_than_minuend,
497            verify_state,
498        )? && self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
499            &subtrahend_less_than_zero,
500            verify_state,
501        )? {
502            return Ok(true);
503        }
504        let minuend_less_than_zero =
505            LessFact::new(minuend.clone(), zero_obj.clone(), line_file.clone()).into();
506        let zero_less_than_subtrahend =
507            LessFact::new(zero_obj, subtrahend.clone(), line_file).into();
508        Ok(
509            self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
510                &minuend_less_than_zero,
511                verify_state,
512            )? && self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
513                &zero_less_than_subtrahend,
514                verify_state,
515            )?,
516        )
517    }
518
519    fn try_verify_not_equal_fact_when_zero_and_binary_arithmetic_reduces_by_operand_facts(
520        &mut self,
521        not_equal_fact: &NotEqualFact,
522        verify_state: &VerifyState,
523    ) -> Result<Option<StmtResult>, RuntimeError> {
524        let line_file = not_equal_fact.line_file.clone();
525        let expression_obj =
526            if self.obj_represents_zero_for_not_equal_builtin_rules(&not_equal_fact.right) {
527                &not_equal_fact.left
528            } else if self.obj_represents_zero_for_not_equal_builtin_rules(&not_equal_fact.left) {
529                &not_equal_fact.right
530            } else {
531                return Ok(None);
532            };
533
534        let builtin_rule_label = match expression_obj {
535            Obj::Add(add) => {
536                if self.both_operands_strictly_positive_by_non_equational_verify(
537                    &add.left,
538                    &add.right,
539                    line_file.clone(),
540                    verify_state,
541                )? {
542                    Some("add_not_equal_zero_both_operands_strictly_positive")
543                } else if self.both_operands_strictly_negative_by_non_equational_verify(
544                    &add.left,
545                    &add.right,
546                    line_file.clone(),
547                    verify_state,
548                )? {
549                    Some("add_not_equal_zero_both_operands_strictly_negative")
550                } else {
551                    None
552                }
553            }
554            Obj::Mul(mul) => {
555                if self.both_operands_nonzero_by_known_non_equational_facts(
556                    &mul.left,
557                    &mul.right,
558                    line_file.clone(),
559                )? {
560                    Some("mul_not_equal_zero_both_factors_nonzero_by_known_facts")
561                } else if self.both_operands_strictly_positive_by_non_equational_verify(
562                    &mul.left,
563                    &mul.right,
564                    line_file.clone(),
565                    verify_state,
566                )? {
567                    Some("mul_not_equal_zero_both_factors_strictly_positive")
568                } else if self.both_operands_strictly_negative_by_non_equational_verify(
569                    &mul.left,
570                    &mul.right,
571                    line_file.clone(),
572                    verify_state,
573                )? {
574                    Some("mul_not_equal_zero_both_factors_strictly_negative")
575                } else {
576                    None
577                }
578            }
579            Obj::Sub(sub) => {
580                if self.sub_difference_nonzero_when_operands_have_strict_opposite_sign_by_non_equational_verify(
581                    &sub.left,
582                    &sub.right,
583                    line_file,
584                    verify_state,
585                )? {
586                    Some("sub_not_equal_zero_operands_strict_opposite_sign")
587                } else {
588                    None
589                }
590            }
591            other => {
592                let zero_obj: Obj = Number::new("0".to_string()).into();
593                let zero_lt_a = LessFact::new(
594                    zero_obj.clone(),
595                    other.clone(),
596                    line_file.clone(),
597                ).into();
598
599                let final_round_verify_state = verify_state.make_final_round_state();
600
601                if self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
602                    &zero_lt_a,
603                    &final_round_verify_state,
604                )? {
605                    Some("not_equal_zero_operand_strictly_positive")
606                } else {
607                    let a_lt_0 = LessFact::new(
608                        other.clone(),
609                        zero_obj,
610                        line_file.clone(),
611                    ).into();
612                    if self.non_equational_atomic_fact_holds_by_full_verify_pipeline(
613                        &a_lt_0,
614                        &final_round_verify_state,
615                    )? {
616                        Some("not_equal_zero_operand_strictly_negative")
617                    } else {
618                        None
619                    }
620                }
621            }
622        };
623
624        match builtin_rule_label {
625            Some(rule_label) => Ok(Some(
626                (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
627                    not_equal_fact.clone().into(),
628                    rule_label.to_string(),
629                    Vec::new(),
630                ))
631                .into(),
632            )),
633            None => Ok(None),
634        }
635    }
636}