Skip to main content

oxilean_std/universal_algebra/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::HashMap;
7
8/// An operation symbol in a signature: name and arity.
9pub struct OpSymbol {
10    pub name: String,
11    pub arity: usize,
12}
13impl OpSymbol {
14    /// Create a new operation symbol.
15    pub fn new(name: &str, arity: usize) -> Self {
16        OpSymbol {
17            name: name.to_string(),
18            arity,
19        }
20    }
21    /// Return true if this operation is a constant (arity 0).
22    pub fn is_constant(&self) -> bool {
23        self.arity == 0
24    }
25}
26/// Boolean algebra structure.
27#[allow(dead_code)]
28#[derive(Debug, Clone)]
29pub struct BooleanAlgebra {
30    pub name: String,
31    pub is_atomic: bool,
32    pub is_complete: bool,
33    pub cardinality_description: String,
34}
35impl BooleanAlgebra {
36    #[allow(dead_code)]
37    pub fn power_set(set_name: &str) -> Self {
38        Self {
39            name: format!("P({})", set_name),
40            is_atomic: true,
41            is_complete: true,
42            cardinality_description: format!("2^|{}|", set_name),
43        }
44    }
45    #[allow(dead_code)]
46    pub fn finite(n: u32) -> Self {
47        Self {
48            name: format!("2^{n}"),
49            is_atomic: true,
50            is_complete: true,
51            cardinality_description: format!("2^{n} elements"),
52        }
53    }
54    #[allow(dead_code)]
55    pub fn stone_representation(&self) -> String {
56        format!(
57            "Stone duality: {} <-> Stone space (compact Hausdorff zero-dimensional)",
58            self.name
59        )
60    }
61    #[allow(dead_code)]
62    pub fn is_representable(&self) -> bool {
63        true
64    }
65}
66/// Distributive lattice (Birkhoff representation).
67#[allow(dead_code)]
68#[derive(Debug, Clone)]
69pub struct DistributiveLattice {
70    pub name: String,
71    pub is_finite: bool,
72}
73impl DistributiveLattice {
74    #[allow(dead_code)]
75    pub fn new(name: &str, finite: bool) -> Self {
76        Self {
77            name: name.to_string(),
78            is_finite: finite,
79        }
80    }
81    #[allow(dead_code)]
82    pub fn birkhoff_representation(&self) -> String {
83        if self.is_finite {
84            format!(
85                "Finite dist. lattice {} <-> J({}) (poset of join-irreducibles)",
86                self.name, self.name
87            )
88        } else {
89            format!(
90                "Infinite dist. lattice {} <-> lattice of down-sets",
91                self.name
92            )
93        }
94    }
95}
96/// A term in a term algebra: either a variable or a function application.
97#[derive(Clone, Debug, PartialEq)]
98pub enum Term {
99    /// A variable with an index.
100    Var(usize),
101    /// An application of an operation symbol to subterms.
102    App { op: String, args: Vec<Term> },
103}
104impl Term {
105    /// Construct a variable term.
106    pub fn var(index: usize) -> Self {
107        Term::Var(index)
108    }
109    /// Construct a function application term.
110    pub fn apply(op: &str, args: Vec<Term>) -> Self {
111        Term::App {
112            op: op.to_string(),
113            args,
114        }
115    }
116    /// Construct a constant term (arity-0 operation).
117    pub fn constant(op: &str) -> Self {
118        Term::App {
119            op: op.to_string(),
120            args: vec![],
121        }
122    }
123    /// Return the depth (maximum nesting) of this term.
124    pub fn depth(&self) -> usize {
125        match self {
126            Term::Var(_) => 0,
127            Term::App { args, .. } => 1 + args.iter().map(|a| a.depth()).max().unwrap_or(0),
128        }
129    }
130    /// Return the set of variable indices appearing in this term.
131    pub fn variables(&self) -> Vec<usize> {
132        let mut vars = Vec::new();
133        self.collect_vars(&mut vars);
134        vars.sort_unstable();
135        vars.dedup();
136        vars
137    }
138    fn collect_vars(&self, acc: &mut Vec<usize>) {
139        match self {
140            Term::Var(i) => acc.push(*i),
141            Term::App { args, .. } => {
142                for a in args {
143                    a.collect_vars(acc);
144                }
145            }
146        }
147    }
148    /// Substitute variable `var_idx` with `replacement` throughout this term.
149    pub fn subst(&self, var_idx: usize, replacement: &Term) -> Term {
150        match self {
151            Term::Var(i) if *i == var_idx => replacement.clone(),
152            Term::Var(_) => self.clone(),
153            Term::App { op, args } => Term::App {
154                op: op.clone(),
155                args: args.iter().map(|a| a.subst(var_idx, replacement)).collect(),
156            },
157        }
158    }
159    /// Evaluate this term in a given algebra, given an assignment of variables to carrier elements.
160    pub fn eval(&self, alg: &Algebra, assignment: &[usize]) -> Option<usize> {
161        match self {
162            Term::Var(i) => assignment.get(*i).copied(),
163            Term::App { op, args } => {
164                let evaluated: Option<Vec<usize>> =
165                    args.iter().map(|a| a.eval(alg, assignment)).collect();
166                let arg_vals = evaluated?;
167                alg.apply_op(op, &arg_vals)
168            }
169        }
170    }
171}
172/// A finite concrete algebra over a signature.
173///
174/// The carrier is `{0, 1, ..., carrier_size - 1}`.
175pub struct Algebra {
176    pub carrier_size: usize,
177    pub signature: Signature,
178    pub tables: std::collections::HashMap<String, Vec<Vec<usize>>>,
179}
180impl Algebra {
181    /// Create a new algebra with the given carrier size and signature.
182    pub fn new(carrier: usize, sig: Signature) -> Self {
183        Algebra {
184            carrier_size: carrier,
185            signature: sig,
186            tables: std::collections::HashMap::new(),
187        }
188    }
189    /// Define the operation table for a named operation.
190    pub fn define_op(&mut self, name: &str, table: Vec<Vec<usize>>) {
191        self.tables.insert(name.to_string(), table);
192    }
193    /// Apply the named operation to the given arguments, returning the result.
194    pub fn apply_op(&self, name: &str, args: &[usize]) -> Option<usize> {
195        let table = self.tables.get(name)?;
196        if args.is_empty() {
197            return table.first().and_then(|row| row.first()).copied();
198        }
199        let current: &Vec<Vec<usize>> = table;
200        let first_arg = *args.first()?;
201        if first_arg >= current.len() {
202            return None;
203        }
204        if args.len() == 1 {
205            return current[first_arg].first().copied();
206        }
207        let second_arg = args[1];
208        let row = current[first_arg].get(second_arg)?;
209        Some(*row)
210    }
211    /// Check whether all table entries refer to elements within the carrier.
212    pub fn is_closed(&self) -> bool {
213        for table in self.tables.values() {
214            for row in table {
215                for &val in row {
216                    if val >= self.carrier_size {
217                        return false;
218                    }
219                }
220            }
221        }
222        true
223    }
224    /// Return a stub count of valid congruences (placeholder implementation).
225    pub fn find_congruences(&self) -> usize {
226        if self.carrier_size <= 1 {
227            1
228        } else {
229            2
230        }
231    }
232}
233/// Birkhoff variety: a class of algebras defined by equations.
234#[allow(dead_code)]
235#[derive(Debug, Clone)]
236pub struct BirkhoffVariety {
237    pub name: String,
238    pub signature_name: String,
239    pub axioms: Vec<String>,
240}
241impl BirkhoffVariety {
242    #[allow(dead_code)]
243    pub fn new(name: &str, sig: &str, axioms: Vec<&str>) -> Self {
244        Self {
245            name: name.to_string(),
246            signature_name: sig.to_string(),
247            axioms: axioms.into_iter().map(String::from).collect(),
248        }
249    }
250    #[allow(dead_code)]
251    pub fn groups() -> Self {
252        Self::new(
253            "Groups",
254            "(*, inv, e)",
255            vec![
256                "x * (y * z) = (x * y) * z",
257                "x * e = x",
258                "e * x = x",
259                "x * inv(x) = e",
260                "inv(x) * x = e",
261            ],
262        )
263    }
264    #[allow(dead_code)]
265    pub fn lattices() -> Self {
266        Self::new(
267            "Lattices",
268            "(meet, join)",
269            vec![
270                "meet(x, x) = x",
271                "join(x, x) = x",
272                "meet(x, y) = meet(y, x)",
273                "join(x, y) = join(y, x)",
274                "meet(x, join(x, y)) = x",
275                "join(x, meet(x, y)) = x",
276            ],
277        )
278    }
279    #[allow(dead_code)]
280    pub fn rings() -> Self {
281        Self::new(
282            "Rings",
283            "(+, *, 0, -, 1)",
284            vec![
285                "x + (y + z) = (x + y) + z",
286                "x + 0 = x",
287                "x + (-x) = 0",
288                "x + y = y + x",
289                "x * (y * z) = (x * y) * z",
290                "x * 1 = x",
291                "1 * x = x",
292                "x * (y + z) = x * y + x * z",
293                "(x + y) * z = x * z + y * z",
294            ],
295        )
296    }
297    #[allow(dead_code)]
298    pub fn is_equationally_definable(&self) -> bool {
299        true
300    }
301    #[allow(dead_code)]
302    pub fn closed_under_hsp(&self) -> bool {
303        true
304    }
305}
306/// Omega-categorical structure.
307#[allow(dead_code)]
308#[derive(Debug, Clone)]
309pub struct OmegaCategoricalStructure {
310    pub name: String,
311    pub automorphism_group_description: String,
312}
313impl OmegaCategoricalStructure {
314    #[allow(dead_code)]
315    pub fn new(name: &str, aut_group: &str) -> Self {
316        Self {
317            name: name.to_string(),
318            automorphism_group_description: aut_group.to_string(),
319        }
320    }
321    #[allow(dead_code)]
322    pub fn is_oligomorphic(&self) -> bool {
323        true
324    }
325    #[allow(dead_code)]
326    pub fn thomas_conjecture(&self) -> String {
327        "Thomas: Constraint satisfaction for omega-categorical structures is in NP or NP-complete or P"
328            .to_string()
329    }
330}
331/// Quasi-variety: class closed under S, P, ultraproducts.
332#[allow(dead_code)]
333#[derive(Debug, Clone)]
334pub struct QuasiVariety {
335    pub name: String,
336    pub quasi_equations: Vec<String>,
337}
338impl QuasiVariety {
339    #[allow(dead_code)]
340    pub fn new(name: &str, eqs: Vec<&str>) -> Self {
341        Self {
342            name: name.to_string(),
343            quasi_equations: eqs.into_iter().map(String::from).collect(),
344        }
345    }
346    #[allow(dead_code)]
347    pub fn every_variety_is_quasivariety(&self) -> bool {
348        true
349    }
350    #[allow(dead_code)]
351    pub fn is_axiomatized_by_implications(&self) -> bool {
352        true
353    }
354}
355/// A rewrite rule: lhs → rhs.
356#[derive(Clone, Debug)]
357pub struct RewriteRule {
358    pub lhs: Term,
359    pub rhs: Term,
360    pub name: String,
361}
362impl RewriteRule {
363    /// Create a new rewrite rule.
364    pub fn new(name: &str, lhs: Term, rhs: Term) -> Self {
365        RewriteRule {
366            lhs,
367            rhs,
368            name: name.to_string(),
369        }
370    }
371    /// Attempt to match `pattern` against `target`, binding variables.
372    /// Returns Some(substitution) if match succeeds; each entry is (var_idx, term).
373    pub fn match_term<'a>(
374        pattern: &'a Term,
375        target: &'a Term,
376        subst: &mut Vec<(usize, Term)>,
377    ) -> bool {
378        match (pattern, target) {
379            (Term::Var(i), _) => {
380                if let Some((_, existing)) = subst.iter().find(|(v, _)| v == i) {
381                    existing == target
382                } else {
383                    subst.push((*i, target.clone()));
384                    true
385                }
386            }
387            (
388                Term::App {
389                    op: op1,
390                    args: args1,
391                },
392                Term::App {
393                    op: op2,
394                    args: args2,
395                },
396            ) => {
397                if op1 != op2 || args1.len() != args2.len() {
398                    return false;
399                }
400                for (a1, a2) in args1.iter().zip(args2.iter()) {
401                    if !Self::match_term(a1, a2, subst) {
402                        return false;
403                    }
404                }
405                true
406            }
407            _ => false,
408        }
409    }
410    /// Apply the substitution to a term.
411    pub fn apply_subst(term: &Term, subst: &[(usize, Term)]) -> Term {
412        let mut result = term.clone();
413        for (var_idx, replacement) in subst {
414            result = result.subst(*var_idx, replacement);
415        }
416        result
417    }
418    /// Try to apply this rule at the top level of `t`.
419    /// Returns Some(rewritten) if the rule fires; None otherwise.
420    pub fn try_apply_top(&self, t: &Term) -> Option<Term> {
421        let mut subst = Vec::new();
422        if Self::match_term(&self.lhs, t, &mut subst) {
423            Some(Self::apply_subst(&self.rhs, &subst))
424        } else {
425            None
426        }
427    }
428}
429/// A simple Knuth-Bendix completion procedure.
430///
431/// Given a set of equations, attempts to produce a confluent and terminating TRS.
432pub struct KnuthBendixCompletion {
433    pub trs: TermRewriteSystem,
434    /// Pending equations (lhs, rhs) to orient.
435    pub pending: Vec<(Term, Term)>,
436    /// Maximum completion steps.
437    pub max_steps: usize,
438}
439impl KnuthBendixCompletion {
440    /// Create a new KB completion instance from initial equations.
441    pub fn new(sig: Signature, equations: Vec<(Term, Term)>, max_steps: usize) -> Self {
442        KnuthBendixCompletion {
443            trs: TermRewriteSystem::new(sig),
444            pending: equations,
445            max_steps,
446        }
447    }
448    /// Orient an equation into a rule using a simple heuristic: deeper lhs on left.
449    fn orient(lhs: &Term, rhs: &Term) -> Option<(Term, Term)> {
450        match lhs.depth().cmp(&rhs.depth()) {
451            std::cmp::Ordering::Greater => Some((lhs.clone(), rhs.clone())),
452            std::cmp::Ordering::Less => Some((rhs.clone(), lhs.clone())),
453            std::cmp::Ordering::Equal => {
454                let lv = lhs.variables().len();
455                let rv = rhs.variables().len();
456                if lv > rv {
457                    Some((lhs.clone(), rhs.clone()))
458                } else if rv > lv {
459                    Some((rhs.clone(), lhs.clone()))
460                } else {
461                    None
462                }
463            }
464        }
465    }
466    /// Run the completion procedure.
467    /// Returns true if completion succeeded (no unorientable equations remain).
468    pub fn run(&mut self) -> bool {
469        for _step in 0..self.max_steps {
470            if self.pending.is_empty() {
471                return true;
472            }
473            let (lhs, rhs) = self.pending.remove(0);
474            let lhs_nf = self.trs.normalize(&lhs, 1000);
475            let rhs_nf = self.trs.normalize(&rhs, 1000);
476            if lhs_nf == rhs_nf {
477                continue;
478            }
479            if let Some((oriented_lhs, oriented_rhs)) = Self::orient(&lhs_nf, &rhs_nf) {
480                let rule = RewriteRule::new("kb_rule", oriented_lhs, oriented_rhs);
481                let mut new_trs = TermRewriteSystem::new(Signature::new());
482                new_trs.rules.clone_from(&self.trs.rules);
483                new_trs.rules.push(rule.clone());
484                let crit_pairs = new_trs.find_critical_pairs();
485                self.trs.add_rule(rule);
486                for (t1, t2) in crit_pairs {
487                    self.pending.push((t1, t2));
488                }
489            } else {
490                return false;
491            }
492        }
493        self.pending.is_empty()
494    }
495    /// Return the resulting confluent TRS rules.
496    pub fn result_rules(&self) -> &[RewriteRule] {
497        &self.trs.rules
498    }
499}
500/// Direct product decomposition theorem.
501#[allow(dead_code)]
502#[derive(Debug, Clone)]
503pub struct DirectProductDecomposition {
504    pub algebra: String,
505    pub factors: Vec<String>,
506}
507impl DirectProductDecomposition {
508    #[allow(dead_code)]
509    pub fn new(alg: &str, factors: Vec<&str>) -> Self {
510        Self {
511            algebra: alg.to_string(),
512            factors: factors.into_iter().map(String::from).collect(),
513        }
514    }
515    #[allow(dead_code)]
516    pub fn is_nontrivial(&self) -> bool {
517        self.factors.len() > 1
518    }
519    #[allow(dead_code)]
520    pub fn subdirectly_irreducible_components(&self) -> Vec<String> {
521        self.factors.clone()
522    }
523}
524/// Clone (universal algebra sense): set of operations closed under composition and containing projections.
525#[allow(dead_code)]
526#[derive(Debug, Clone)]
527pub struct Clone {
528    pub name: String,
529    pub contains_projections: bool,
530    pub closed_under_composition: bool,
531}
532impl Clone {
533    #[allow(dead_code)]
534    pub fn new(name: &str) -> Self {
535        Self {
536            name: name.to_string(),
537            contains_projections: true,
538            closed_under_composition: true,
539        }
540    }
541    #[allow(dead_code)]
542    pub fn clone_of_algebra(algebra: &str) -> Self {
543        Self::new(&format!("Clo({})", algebra))
544    }
545    #[allow(dead_code)]
546    pub fn galois_connection_description(&self) -> String {
547        format!(
548            "Galois connection: Clo <-> Inv (polymorphisms and invariant relations for {})",
549            self.name
550        )
551    }
552}
553/// The term algebra for a signature and variable set.
554///
555/// Stores the signature and supports term construction/evaluation.
556pub struct TermAlgebra {
557    pub signature: Signature,
558    pub num_vars: usize,
559}
560impl TermAlgebra {
561    /// Create a term algebra with the given signature and number of variables.
562    pub fn new(sig: Signature, num_vars: usize) -> Self {
563        TermAlgebra {
564            signature: sig,
565            num_vars,
566        }
567    }
568    /// Validate that a term is well-formed with respect to the signature.
569    pub fn is_well_formed(&self, term: &Term) -> bool {
570        match term {
571            Term::Var(i) => *i < self.num_vars,
572            Term::App { op, args } => {
573                if let Some(sym) = self.signature.operations.iter().find(|s| s.name == *op) {
574                    sym.arity == args.len() && args.iter().all(|a| self.is_well_formed(a))
575                } else {
576                    false
577                }
578            }
579        }
580    }
581    /// Build the term `op(x_0, x_1, ..., x_{arity-1})` for a given op name.
582    pub fn free_term(&self, op_name: &str) -> Option<Term> {
583        let sym = self
584            .signature
585            .operations
586            .iter()
587            .find(|s| s.name == *op_name)?;
588        let args: Vec<Term> = (0..sym.arity).map(Term::var).collect();
589        Some(Term::apply(op_name, args))
590    }
591}
592/// Tarski's characterization of representable relation algebras.
593#[allow(dead_code)]
594#[derive(Debug, Clone)]
595pub struct RelationAlgebra {
596    pub name: String,
597    pub is_representable: bool,
598    pub is_simple: bool,
599}
600impl RelationAlgebra {
601    #[allow(dead_code)]
602    pub fn new(name: &str, rep: bool, simple: bool) -> Self {
603        Self {
604            name: name.to_string(),
605            is_representable: rep,
606            is_simple: simple,
607        }
608    }
609    #[allow(dead_code)]
610    pub fn operators_description(&self) -> String {
611        "(+, *, -, 0, 1, ;, ^, 1')".to_string()
612    }
613    #[allow(dead_code)]
614    pub fn representable_iff_field_of_sets(&self) -> bool {
615        self.is_representable
616    }
617}
618/// A congruence relation on a finite algebra, represented as a partition
619/// of carrier elements into equivalence classes.
620pub struct CongruenceRelation {
621    /// `parent[i]` is the representative of element `i` (union-find style).
622    parent: Vec<usize>,
623    /// Size of the carrier.
624    pub carrier_size: usize,
625}
626impl CongruenceRelation {
627    /// Create the discrete congruence (equality) on a carrier of given size.
628    pub fn discrete(carrier_size: usize) -> Self {
629        CongruenceRelation {
630            parent: (0..carrier_size).collect(),
631            carrier_size,
632        }
633    }
634    /// Create the total congruence (all elements equivalent).
635    pub fn total(carrier_size: usize) -> Self {
636        let mut rel = Self::discrete(carrier_size);
637        for i in 1..carrier_size {
638            rel.merge(0, i);
639        }
640        rel
641    }
642    /// Find the representative of the class containing `x`.
643    pub fn find(&mut self, x: usize) -> usize {
644        if self.parent[x] != x {
645            let root = self.find(self.parent[x]);
646            self.parent[x] = root;
647        }
648        self.parent[x]
649    }
650    /// Merge the classes of `x` and `y`.
651    pub fn merge(&mut self, x: usize, y: usize) {
652        let rx = self.find(x);
653        let ry = self.find(y);
654        if rx != ry {
655            self.parent[ry] = rx;
656        }
657    }
658    /// Return true iff `x` and `y` are in the same equivalence class.
659    pub fn are_equiv(&mut self, x: usize, y: usize) -> bool {
660        self.find(x) == self.find(y)
661    }
662    /// Return the equivalence classes as a list of sorted lists.
663    pub fn classes(&mut self) -> Vec<Vec<usize>> {
664        let mut map: std::collections::HashMap<usize, Vec<usize>> =
665            std::collections::HashMap::new();
666        for i in 0..self.carrier_size {
667            let root = self.find(i);
668            map.entry(root).or_default().push(i);
669        }
670        let mut classes: Vec<Vec<usize>> = map.into_values().collect();
671        for cls in &mut classes {
672            cls.sort_unstable();
673        }
674        classes.sort_by_key(|c| c[0]);
675        classes
676    }
677    /// Return the number of equivalence classes.
678    pub fn num_classes(&mut self) -> usize {
679        self.classes().len()
680    }
681}
682/// A term rewriting system: a collection of rewrite rules.
683pub struct TermRewriteSystem {
684    pub rules: Vec<RewriteRule>,
685    pub signature: Signature,
686}
687impl TermRewriteSystem {
688    /// Create an empty TRS.
689    pub fn new(sig: Signature) -> Self {
690        TermRewriteSystem {
691            rules: Vec::new(),
692            signature: sig,
693        }
694    }
695    /// Add a rewrite rule.
696    pub fn add_rule(&mut self, rule: RewriteRule) {
697        self.rules.push(rule);
698    }
699    /// Perform one outermost-innermost rewrite step on `t`.
700    /// Returns Some(result) if any rule applies; None if already normal.
701    pub fn rewrite_step(&self, t: &Term) -> Option<Term> {
702        for rule in &self.rules {
703            if let Some(result) = rule.try_apply_top(t) {
704                return Some(result);
705            }
706        }
707        match t {
708            Term::Var(_) => None,
709            Term::App { op, args } => {
710                let mut new_args = args.clone();
711                for (i, arg) in args.iter().enumerate() {
712                    if let Some(new_arg) = self.rewrite_step(arg) {
713                        new_args[i] = new_arg;
714                        return Some(Term::App {
715                            op: op.clone(),
716                            args: new_args,
717                        });
718                    }
719                }
720                None
721            }
722        }
723    }
724    /// Reduce `t` to normal form (up to a step limit to avoid infinite loops).
725    pub fn normalize(&self, t: &Term, max_steps: usize) -> Term {
726        let mut current = t.clone();
727        for _ in 0..max_steps {
728            match self.rewrite_step(&current) {
729                Some(next) => current = next,
730                None => break,
731            }
732        }
733        current
734    }
735    /// Check for local confluence by testing critical pairs (simplified).
736    /// Returns a list of (t1, t2) pairs where t1 and t2 are the two reducts
737    /// of a critical overlap between rules i and j.
738    pub fn find_critical_pairs(&self) -> Vec<(Term, Term)> {
739        let mut pairs = Vec::new();
740        for (i, r1) in self.rules.iter().enumerate() {
741            for (j, r2) in self.rules.iter().enumerate() {
742                if i == j {
743                    continue;
744                }
745                let mut subst1 = Vec::new();
746                let mut subst2 = Vec::new();
747                if RewriteRule::match_term(&r1.lhs, &r2.lhs, &mut subst1)
748                    && RewriteRule::match_term(&r2.lhs, &r1.lhs, &mut subst2)
749                {
750                    let t1 = RewriteRule::apply_subst(&r1.rhs, &subst1);
751                    let t2 = RewriteRule::apply_subst(&r2.rhs, &subst2);
752                    pairs.push((t1, t2));
753                }
754            }
755        }
756        pairs
757    }
758}
759/// An algebraic signature: a collection of operation symbols.
760pub struct Signature {
761    pub operations: Vec<OpSymbol>,
762}
763impl Signature {
764    /// Create an empty signature.
765    pub fn new() -> Self {
766        Signature {
767            operations: Vec::new(),
768        }
769    }
770    /// Add an operation symbol to the signature.
771    pub fn add_op(&mut self, op: OpSymbol) {
772        self.operations.push(op);
773    }
774    /// Return the list of (name, arity) pairs.
775    pub fn arities(&self) -> Vec<(String, usize)> {
776        self.operations
777            .iter()
778            .map(|op| (op.name.clone(), op.arity))
779            .collect()
780    }
781    /// Check whether an operation with the given name exists.
782    pub fn has_op(&self, name: &str) -> bool {
783        self.operations.iter().any(|op| op.name == name)
784    }
785}
786/// An equational law: a named identity lhs ≈ rhs (as strings/term descriptions).
787pub struct EquationalLaw {
788    pub name: String,
789    pub lhs: String,
790    pub rhs: String,
791}
792impl EquationalLaw {
793    /// Create a new equational law.
794    pub fn new(name: &str, lhs: &str, rhs: &str) -> Self {
795        EquationalLaw {
796            name: name.to_string(),
797            lhs: lhs.to_string(),
798            rhs: rhs.to_string(),
799        }
800    }
801    /// Return true if the law is trivially true (lhs == rhs syntactically).
802    pub fn is_trivial(&self) -> bool {
803        self.lhs == self.rhs
804    }
805}
806/// Simple rewriting system with string rules (distinct from TermRewriteSystem above).
807#[allow(dead_code)]
808#[derive(Debug, Clone)]
809pub struct SimpleRewriteSystem {
810    pub name: String,
811    pub rules: Vec<(String, String)>,
812    pub is_confluent: bool,
813    pub is_terminating: bool,
814}
815impl SimpleRewriteSystem {
816    #[allow(dead_code)]
817    pub fn new(name: &str) -> Self {
818        Self {
819            name: name.to_string(),
820            rules: Vec::new(),
821            is_confluent: false,
822            is_terminating: false,
823        }
824    }
825    #[allow(dead_code)]
826    pub fn add_rule(&mut self, lhs: &str, rhs: &str) {
827        self.rules.push((lhs.to_string(), rhs.to_string()));
828    }
829    #[allow(dead_code)]
830    pub fn is_complete(&self) -> bool {
831        self.is_confluent && self.is_terminating
832    }
833    #[allow(dead_code)]
834    pub fn normal_form_unique(&self) -> bool {
835        self.is_complete()
836    }
837    #[allow(dead_code)]
838    pub fn critical_pairs_description(&self) -> String {
839        format!("Critical pairs for {}: overlaps of LHS of rules", self.name)
840    }
841}
842/// Algebra homomorphism.
843#[allow(dead_code)]
844#[derive(Debug, Clone)]
845pub struct AlgebraHomomorphism {
846    pub source: String,
847    pub target: String,
848    pub is_injective: bool,
849    pub is_surjective: bool,
850}
851impl AlgebraHomomorphism {
852    #[allow(dead_code)]
853    pub fn new(src: &str, tgt: &str, inj: bool, surj: bool) -> Self {
854        Self {
855            source: src.to_string(),
856            target: tgt.to_string(),
857            is_injective: inj,
858            is_surjective: surj,
859        }
860    }
861    #[allow(dead_code)]
862    pub fn is_isomorphism(&self) -> bool {
863        self.is_injective && self.is_surjective
864    }
865    #[allow(dead_code)]
866    pub fn is_embedding(&self) -> bool {
867        self.is_injective
868    }
869}
870/// Free algebra in a variety.
871#[allow(dead_code)]
872#[derive(Debug, Clone)]
873pub struct FreeAlgebra {
874    pub variety_name: String,
875    pub num_generators: usize,
876}
877impl FreeAlgebra {
878    #[allow(dead_code)]
879    pub fn new(variety: &str, generators: usize) -> Self {
880        Self {
881            variety_name: variety.to_string(),
882            num_generators: generators,
883        }
884    }
885    #[allow(dead_code)]
886    pub fn universal_property(&self) -> String {
887        format!(
888            "Free {}-algebra on {} generators: any map to any {}-algebra extends uniquely",
889            self.variety_name, self.num_generators, self.variety_name
890        )
891    }
892    #[allow(dead_code)]
893    pub fn word_problem_decidable(&self) -> bool {
894        true
895    }
896}
897/// Maltsev conditions.
898#[allow(dead_code)]
899#[derive(Debug, Clone)]
900pub struct MaltsevCondition {
901    pub name: String,
902    pub terms: Vec<String>,
903    pub equations: Vec<String>,
904}
905impl MaltsevCondition {
906    #[allow(dead_code)]
907    pub fn congruence_permutability() -> Self {
908        Self {
909            name: "Congruence permutability".to_string(),
910            terms: vec!["p(x,y,z)".to_string()],
911            equations: vec!["p(x,x,y) = y".to_string(), "p(x,y,y) = x".to_string()],
912        }
913    }
914    #[allow(dead_code)]
915    pub fn congruence_distributivity(n: usize) -> Self {
916        Self {
917            name: format!("Congruence distributivity (Jonsson n={})", n),
918            terms: (0..=n).map(|i| format!("d_{i}(x,y,z)")).collect(),
919            equations: vec![
920                "d_0(x,y,z) = x".to_string(),
921                format!("d_{}(x,y,z) = z", n),
922                "d_i(x,x,z) = d_{i+1}(x,x,z) for even i".to_string(),
923                "d_i(x,z,z) = d_{i+1}(x,z,z) for odd i".to_string(),
924            ],
925        }
926    }
927    #[allow(dead_code)]
928    pub fn characterizes_variety(&self) -> bool {
929        true
930    }
931}
932/// A variety: an equationally defined class of algebras.
933pub struct Variety {
934    pub name: String,
935    pub laws: Vec<EquationalLaw>,
936    pub signature: Signature,
937}
938impl Variety {
939    /// Create a new variety with the given name and signature.
940    pub fn new(name: &str, sig: Signature) -> Self {
941        Variety {
942            name: name.to_string(),
943            laws: Vec::new(),
944            signature: sig,
945        }
946    }
947    /// Add an equational law to the variety.
948    pub fn add_law(&mut self, law: EquationalLaw) {
949        self.laws.push(law);
950    }
951    /// Return true if the variety is (at least axiomatically) a group variety,
952    /// i.e. it includes associativity, identity, and inverse laws.
953    pub fn is_group_variety(&self) -> bool {
954        self.includes_law("assoc") && self.includes_law("identity") && self.includes_law("inverse")
955    }
956    /// Return true if the variety contains a law with the given name.
957    pub fn includes_law(&self, name: &str) -> bool {
958        self.laws.iter().any(|l| l.name == name)
959    }
960}
961/// Congruence lattice of an algebra.
962#[allow(dead_code)]
963#[derive(Debug, Clone)]
964pub struct CongruenceLattice {
965    pub algebra_name: String,
966    pub is_distributive: bool,
967    pub is_modular: bool,
968}
969impl CongruenceLattice {
970    #[allow(dead_code)]
971    pub fn new(name: &str, dist: bool, modular: bool) -> Self {
972        Self {
973            algebra_name: name.to_string(),
974            is_distributive: dist,
975            is_modular: modular,
976        }
977    }
978    #[allow(dead_code)]
979    pub fn has_permuting_congruences(&self) -> bool {
980        false
981    }
982    #[allow(dead_code)]
983    pub fn jonssontheorem_description(&self) -> String {
984        format!(
985            "Jonsson's theorem: Subdirectly irreducible members of variety of {}",
986            self.algebra_name
987        )
988    }
989}