1#[cfg(not(feature = "std"))]
5use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
6#[cfg(feature = "std")]
7use std::{format, string::String, vec, vec::Vec};
8
9use karpal_proof::{
10 AdditivelyCommutative, HasLeftIdentity, HasLeftInverse, HasRightIdentity, HasRightInverse,
11 IsAbsorptive, IsAssociative, IsCommutative, IsDistributive, IsIdempotent, IsMonoid, Property,
12};
13
14use crate::signature::AlgebraicSignature;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum VerificationTier {
19 Impossible,
21 Rare,
23 Emergent,
25 External,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ProofDialect {
32 SmtLib2,
33 Lean4,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Origin {
39 pub crate_name: String,
40 pub item_path: String,
41}
42
43impl Origin {
44 pub fn new(crate_name: impl Into<String>, item_path: impl Into<String>) -> Self {
45 Self {
46 crate_name: crate_name.into(),
47 item_path: item_path.into(),
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Declaration {
55 pub name: String,
56 pub sort: Sort,
57}
58
59impl Declaration {
60 pub fn new(name: impl Into<String>, sort: Sort) -> Self {
61 Self {
62 name: name.into(),
63 sort,
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum Sort {
71 Bool,
72 Int,
73 Real,
74 Named(String),
75}
76
77impl Sort {
78 pub fn named(name: impl Into<String>) -> Self {
79 Self::Named(name.into())
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum Term {
86 Var(String),
87 Bool(bool),
88 Int(i64),
89 App { function: String, args: Vec<Term> },
90 Eq(Box<Term>, Box<Term>),
91 And(Vec<Term>),
92 Or(Vec<Term>),
93 Not(Box<Term>),
94 Implies(Box<Term>, Box<Term>),
95}
96
97impl Term {
98 pub fn var(name: impl Into<String>) -> Self {
99 Self::Var(name.into())
100 }
101
102 pub fn bool(value: bool) -> Self {
103 Self::Bool(value)
104 }
105
106 pub fn int(value: i64) -> Self {
107 Self::Int(value)
108 }
109
110 pub fn app(function: impl Into<String>, args: impl IntoIterator<Item = Term>) -> Self {
111 Self::App {
112 function: function.into(),
113 args: args.into_iter().collect(),
114 }
115 }
116
117 pub fn eq(left: Term, right: Term) -> Self {
118 Self::Eq(Box::new(left), Box::new(right))
119 }
120
121 pub fn and(terms: impl IntoIterator<Item = Term>) -> Self {
122 Self::And(terms.into_iter().collect())
123 }
124
125 pub fn or(terms: impl IntoIterator<Item = Term>) -> Self {
126 Self::Or(terms.into_iter().collect())
127 }
128
129 pub fn negate(term: Term) -> Self {
130 Self::Not(Box::new(term))
131 }
132
133 pub fn implies(lhs: Term, rhs: Term) -> Self {
134 Self::Implies(Box::new(lhs), Box::new(rhs))
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct Obligation {
141 pub name: String,
142 pub property: &'static str,
143 pub declarations: Vec<Declaration>,
144 pub assumptions: Vec<Term>,
145 pub conclusion: Term,
146 pub origin: Origin,
147 pub tier: VerificationTier,
148}
149
150impl Obligation {
151 pub fn for_property<P: Property>(
153 name: impl Into<String>,
154 origin: Origin,
155 tier: VerificationTier,
156 conclusion: Term,
157 ) -> Self {
158 Self {
159 name: name.into(),
160 property: P::NAME,
161 declarations: Vec::new(),
162 assumptions: Vec::new(),
163 conclusion,
164 origin,
165 tier,
166 }
167 }
168
169 pub fn with_decl(mut self, name: impl Into<String>, sort: Sort) -> Self {
170 self.declarations.push(Declaration::new(name, sort));
171 self
172 }
173
174 pub fn with_assumption(mut self, term: Term) -> Self {
175 self.assumptions.push(term);
176 self
177 }
178
179 pub fn associativity(
181 name: impl Into<String>,
182 origin: Origin,
183 carrier: Sort,
184 op: impl Into<String>,
185 ) -> Self {
186 let op = op.into();
187 let (a, b, c) = (Term::var("a"), Term::var("b"), Term::var("c"));
188 let left = Term::app(
189 op.clone(),
190 vec![Term::app(op.clone(), vec![a.clone(), b.clone()]), c.clone()],
191 );
192 let right = Term::app(
193 op.clone(),
194 vec![a.clone(), Term::app(op, vec![b.clone(), c])],
195 );
196
197 Self::for_property::<IsAssociative>(
198 name,
199 origin,
200 VerificationTier::External,
201 Term::eq(left, right),
202 )
203 .with_decl("a", carrier.clone())
204 .with_decl("b", carrier.clone())
205 .with_decl("c", carrier)
206 }
207
208 pub fn associativity_in(
209 name: impl Into<String>,
210 origin: Origin,
211 signature: &AlgebraicSignature,
212 role: &str,
213 ) -> Self {
214 Self::associativity(
215 name,
216 origin,
217 signature.carrier.clone(),
218 signature.require_binary(role),
219 )
220 }
221
222 pub fn commutativity(
224 name: impl Into<String>,
225 origin: Origin,
226 carrier: Sort,
227 op: impl Into<String>,
228 ) -> Self {
229 let op = op.into();
230 let (a, b) = (Term::var("a"), Term::var("b"));
231 let left = Term::app(op.clone(), vec![a.clone(), b.clone()]);
232 let right = Term::app(op, vec![b, a]);
233
234 Self::for_property::<IsCommutative>(
235 name,
236 origin,
237 VerificationTier::External,
238 Term::eq(left, right),
239 )
240 .with_decl("a", carrier.clone())
241 .with_decl("b", carrier)
242 }
243
244 pub fn commutativity_in(
245 name: impl Into<String>,
246 origin: Origin,
247 signature: &AlgebraicSignature,
248 role: &str,
249 ) -> Self {
250 Self::commutativity(
251 name,
252 origin,
253 signature.carrier.clone(),
254 signature.require_binary(role),
255 )
256 }
257
258 pub fn additive_commutativity(
259 name: impl Into<String>,
260 origin: Origin,
261 carrier: Sort,
262 add: impl Into<String>,
263 ) -> Self {
264 let add = add.into();
265 let (a, b) = (Term::var("a"), Term::var("b"));
266 let left = Term::app(add.clone(), vec![a.clone(), b.clone()]);
267 let right = Term::app(add, vec![b, a]);
268
269 Self::for_property::<AdditivelyCommutative>(
270 name,
271 origin,
272 VerificationTier::External,
273 Term::eq(left, right),
274 )
275 .with_decl("a", carrier.clone())
276 .with_decl("b", carrier)
277 }
278
279 pub fn additive_commutativity_in(
280 name: impl Into<String>,
281 origin: Origin,
282 signature: &AlgebraicSignature,
283 ) -> Self {
284 Self::additive_commutativity(
285 name,
286 origin,
287 signature.carrier.clone(),
288 signature.require_binary("add"),
289 )
290 }
291
292 pub fn monoid_identity(
294 name: impl Into<String>,
295 origin: Origin,
296 carrier: Sort,
297 op: impl Into<String>,
298 identity: impl Into<String>,
299 ) -> Self {
300 let op = op.into();
301 let e = Term::var(identity);
302 let a = Term::var("a");
303 let left = Term::eq(Term::app(op.clone(), vec![e.clone(), a.clone()]), a.clone());
304 let right = Term::eq(Term::app(op, vec![a.clone(), e]), a);
305
306 Self::for_property::<IsMonoid>(
307 name,
308 origin,
309 VerificationTier::External,
310 Term::and(vec![left, right]),
311 )
312 .with_decl("a", carrier.clone())
313 .with_decl("e", carrier)
314 }
315
316 pub fn monoid_identity_in(
317 name: impl Into<String>,
318 origin: Origin,
319 signature: &AlgebraicSignature,
320 ) -> Self {
321 Self::monoid_identity(
322 name,
323 origin,
324 signature.carrier.clone(),
325 signature.require_binary("combine"),
326 signature.require_constant("identity"),
327 )
328 }
329
330 pub fn left_identity(
331 name: impl Into<String>,
332 origin: Origin,
333 carrier: Sort,
334 op: impl Into<String>,
335 identity: impl Into<String>,
336 ) -> Self {
337 let op = op.into();
338 let e = Term::var(identity);
339 let a = Term::var("a");
340 let conclusion = Term::eq(Term::app(op, vec![e, a.clone()]), a);
341
342 Self::for_property::<HasLeftIdentity>(name, origin, VerificationTier::External, conclusion)
343 .with_decl("a", carrier.clone())
344 .with_decl("e", carrier)
345 }
346
347 pub fn left_identity_in(
348 name: impl Into<String>,
349 origin: Origin,
350 signature: &AlgebraicSignature,
351 op_role: &str,
352 identity_role: &str,
353 ) -> Self {
354 Self::left_identity(
355 name,
356 origin,
357 signature.carrier.clone(),
358 signature.require_binary(op_role),
359 signature.require_constant(identity_role),
360 )
361 }
362
363 pub fn right_identity(
364 name: impl Into<String>,
365 origin: Origin,
366 carrier: Sort,
367 op: impl Into<String>,
368 identity: impl Into<String>,
369 ) -> Self {
370 let op = op.into();
371 let e = Term::var(identity);
372 let a = Term::var("a");
373 let conclusion = Term::eq(Term::app(op, vec![a.clone(), e]), a);
374
375 Self::for_property::<HasRightIdentity>(name, origin, VerificationTier::External, conclusion)
376 .with_decl("a", carrier.clone())
377 .with_decl("e", carrier)
378 }
379
380 pub fn right_identity_in(
381 name: impl Into<String>,
382 origin: Origin,
383 signature: &AlgebraicSignature,
384 op_role: &str,
385 identity_role: &str,
386 ) -> Self {
387 Self::right_identity(
388 name,
389 origin,
390 signature.carrier.clone(),
391 signature.require_binary(op_role),
392 signature.require_constant(identity_role),
393 )
394 }
395
396 pub fn left_inverse(
397 name: impl Into<String>,
398 origin: Origin,
399 carrier: Sort,
400 op: impl Into<String>,
401 inverse: impl Into<String>,
402 identity: impl Into<String>,
403 ) -> Self {
404 let op = op.into();
405 let inv = inverse.into();
406 let e = Term::var(identity);
407 let a = Term::var("a");
408 let conclusion = Term::eq(Term::app(op, vec![Term::app(inv, vec![a.clone()]), a]), e);
409
410 Self::for_property::<HasLeftInverse>(name, origin, VerificationTier::External, conclusion)
411 .with_decl("a", carrier.clone())
412 .with_decl("e", carrier)
413 }
414
415 pub fn left_inverse_in(
416 name: impl Into<String>,
417 origin: Origin,
418 signature: &AlgebraicSignature,
419 ) -> Self {
420 Self::left_inverse(
421 name,
422 origin,
423 signature.carrier.clone(),
424 signature.require_binary("combine"),
425 signature.require_unary("inverse"),
426 signature.require_constant("identity"),
427 )
428 }
429
430 pub fn right_inverse(
431 name: impl Into<String>,
432 origin: Origin,
433 carrier: Sort,
434 op: impl Into<String>,
435 inverse: impl Into<String>,
436 identity: impl Into<String>,
437 ) -> Self {
438 let op = op.into();
439 let inv = inverse.into();
440 let e = Term::var(identity);
441 let a = Term::var("a");
442 let conclusion = Term::eq(Term::app(op, vec![a.clone(), Term::app(inv, vec![a])]), e);
443
444 Self::for_property::<HasRightInverse>(name, origin, VerificationTier::External, conclusion)
445 .with_decl("a", carrier.clone())
446 .with_decl("e", carrier)
447 }
448
449 pub fn right_inverse_in(
450 name: impl Into<String>,
451 origin: Origin,
452 signature: &AlgebraicSignature,
453 ) -> Self {
454 Self::right_inverse(
455 name,
456 origin,
457 signature.carrier.clone(),
458 signature.require_binary("combine"),
459 signature.require_unary("inverse"),
460 signature.require_constant("identity"),
461 )
462 }
463
464 pub fn left_distributivity(
465 name: impl Into<String>,
466 origin: Origin,
467 carrier: Sort,
468 add: impl Into<String>,
469 mul: impl Into<String>,
470 ) -> Self {
471 let add = add.into();
472 let mul = mul.into();
473 let (a, b, c) = (Term::var("a"), Term::var("b"), Term::var("c"));
474 let left = Term::app(
475 mul.clone(),
476 vec![
477 a.clone(),
478 Term::app(add.clone(), vec![b.clone(), c.clone()]),
479 ],
480 );
481 let right = Term::app(
482 add,
483 vec![
484 Term::app(mul.clone(), vec![a.clone(), b]),
485 Term::app(mul, vec![a, c]),
486 ],
487 );
488
489 Self::for_property::<IsDistributive>(
490 name,
491 origin,
492 VerificationTier::External,
493 Term::eq(left, right),
494 )
495 .with_decl("a", carrier.clone())
496 .with_decl("b", carrier.clone())
497 .with_decl("c", carrier)
498 }
499
500 pub fn left_distributivity_in(
501 name: impl Into<String>,
502 origin: Origin,
503 signature: &AlgebraicSignature,
504 ) -> Self {
505 Self::left_distributivity(
506 name,
507 origin,
508 signature.carrier.clone(),
509 signature.require_binary("add"),
510 signature.require_binary("mul"),
511 )
512 }
513
514 pub fn right_distributivity(
515 name: impl Into<String>,
516 origin: Origin,
517 carrier: Sort,
518 add: impl Into<String>,
519 mul: impl Into<String>,
520 ) -> Self {
521 let add = add.into();
522 let mul = mul.into();
523 let (a, b, c) = (Term::var("a"), Term::var("b"), Term::var("c"));
524 let left = Term::app(
525 mul.clone(),
526 vec![
527 Term::app(add.clone(), vec![a.clone(), b.clone()]),
528 c.clone(),
529 ],
530 );
531 let right = Term::app(
532 add,
533 vec![
534 Term::app(mul.clone(), vec![a, c.clone()]),
535 Term::app(mul, vec![b, c]),
536 ],
537 );
538
539 Self::for_property::<IsDistributive>(
540 name,
541 origin,
542 VerificationTier::External,
543 Term::eq(left, right),
544 )
545 .with_decl("a", carrier.clone())
546 .with_decl("b", carrier.clone())
547 .with_decl("c", carrier)
548 }
549
550 pub fn right_distributivity_in(
551 name: impl Into<String>,
552 origin: Origin,
553 signature: &AlgebraicSignature,
554 ) -> Self {
555 Self::right_distributivity(
556 name,
557 origin,
558 signature.carrier.clone(),
559 signature.require_binary("add"),
560 signature.require_binary("mul"),
561 )
562 }
563
564 pub fn idempotency(
565 name: impl Into<String>,
566 origin: Origin,
567 carrier: Sort,
568 op: impl Into<String>,
569 ) -> Self {
570 let op = op.into();
571 let a = Term::var("a");
572 let conclusion = Term::eq(Term::app(op, vec![a.clone(), a.clone()]), a);
573
574 Self::for_property::<IsIdempotent>(name, origin, VerificationTier::External, conclusion)
575 .with_decl("a", carrier)
576 }
577
578 pub fn idempotency_in(
579 name: impl Into<String>,
580 origin: Origin,
581 signature: &AlgebraicSignature,
582 role: &str,
583 ) -> Self {
584 Self::idempotency(
585 name,
586 origin,
587 signature.carrier.clone(),
588 signature.require_binary(role),
589 )
590 }
591
592 pub fn absorption(
593 name: impl Into<String>,
594 origin: Origin,
595 carrier: Sort,
596 meet: impl Into<String>,
597 join: impl Into<String>,
598 ) -> Self {
599 let meet = meet.into();
600 let join = join.into();
601 let (a, b) = (Term::var("a"), Term::var("b"));
602 let left = Term::eq(
603 Term::app(
604 meet.clone(),
605 vec![
606 a.clone(),
607 Term::app(join.clone(), vec![a.clone(), b.clone()]),
608 ],
609 ),
610 a.clone(),
611 );
612 let right = Term::eq(
613 Term::app(join, vec![a.clone(), Term::app(meet, vec![a.clone(), b])]),
614 a,
615 );
616
617 Self::for_property::<IsAbsorptive>(
618 name,
619 origin,
620 VerificationTier::External,
621 Term::and(vec![left, right]),
622 )
623 .with_decl("a", carrier.clone())
624 .with_decl("b", carrier)
625 }
626
627 pub fn absorption_in(
628 name: impl Into<String>,
629 origin: Origin,
630 signature: &AlgebraicSignature,
631 ) -> Self {
632 Self::absorption(
633 name,
634 origin,
635 signature.carrier.clone(),
636 signature.require_binary("meet"),
637 signature.require_binary("join"),
638 )
639 }
640
641 pub fn summary(&self) -> String {
643 format!(
644 "{}::{} [{}]",
645 self.origin.crate_name, self.origin.item_path, self.property
646 )
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use crate::signature::AlgebraicSignature;
654
655 #[test]
656 fn associativity_builder_populates_metadata() {
657 let obligation = Obligation::associativity(
658 "option_add_assoc",
659 Origin::new("karpal-algebra", "Semigroup for Sum<i32>"),
660 Sort::Int,
661 "combine",
662 );
663
664 assert_eq!(obligation.property, IsAssociative::NAME);
665 assert_eq!(obligation.declarations.len(), 3);
666 assert!(obligation.summary().contains("associativity"));
667 }
668
669 #[test]
670 fn monoid_identity_builder_creates_conjunction() {
671 let obligation = Obligation::monoid_identity(
672 "sum_identity",
673 Origin::new("karpal-core", "Monoid for Sum<i32>"),
674 Sort::Int,
675 "combine",
676 "e",
677 );
678
679 assert_eq!(obligation.property, IsMonoid::NAME);
680 assert!(matches!(obligation.conclusion, Term::And(_)));
681 }
682
683 #[test]
684 fn signature_driven_group_builders_use_registered_roles() {
685 let sig = AlgebraicSignature::group(Sort::Int, "combine", "e", "inv");
686 let left = Obligation::left_inverse_in("left_inv", Origin::new("k", "G"), &sig);
687 let right = Obligation::right_inverse_in("right_inv", Origin::new("k", "G"), &sig);
688
689 assert_eq!(left.property, HasLeftInverse::NAME);
690 assert_eq!(right.property, HasRightInverse::NAME);
691 }
692
693 #[test]
694 fn lattice_absorption_creates_conjunction() {
695 let sig = AlgebraicSignature::lattice(Sort::Int, "meet", "join");
696 let obligation = Obligation::absorption_in("lattice_absorb", Origin::new("k", "L"), &sig);
697 assert_eq!(obligation.property, IsAbsorptive::NAME);
698 assert!(matches!(obligation.conclusion, Term::And(_)));
699 }
700}