Skip to main content

simular/edd/
model_card.rs

1//! Equation Model Card (EMC) - Mandatory documentation for EDD simulations.
2//!
3//! The EMC bridges mathematics and code, ensuring every simulation is grounded
4//! in peer-reviewed theory. No simulation can run without a complete EMC.
5//!
6//! # EMC Schema (9 Required Sections)
7//!
8//! 1. **Identity**: Name, UUID, version
9//! 2. **Governing Equation**: LaTeX, analytical derivation
10//! 3. **Variables**: All parameters with units and constraints
11//! 4. **Verification Tests**: Analytical solutions for TDD
12//! 5. **Domain Constraints**: Valid operating ranges
13//! 6. **Falsification Criteria**: How to disprove the model
14//! 7. **References**: Peer-reviewed citations
15//! 8. **Implementation Notes**: Numerical considerations
16//! 9. **Lineage**: Parent equations, derivations
17
18use super::equation::{Citation, EquationClass, EquationVariable, GoverningEquation};
19use std::collections::HashMap;
20
21// Re-export Citation for convenience
22pub use super::equation::Citation as EmcCitation;
23
24/// Domain constraint specifying valid operating ranges.
25#[derive(Debug, Clone)]
26pub struct DomainConstraint {
27    /// Name of the constraint
28    pub name: String,
29    /// Mathematical expression (e.g., "0 < ρ < 1")
30    pub expression: String,
31    /// Rationale for this constraint
32    pub rationale: String,
33    /// What happens when violated
34    pub violation_behavior: ViolationBehavior,
35}
36
37/// Behavior when a domain constraint is violated.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ViolationBehavior {
40    /// Log warning but continue
41    Warn,
42    /// Halt simulation (Jidoka)
43    Halt,
44    /// Clamp value to valid range
45    Clamp,
46    /// Return special value (NaN, Infinity)
47    Special,
48}
49
50/// A verification test case with known analytical solution.
51#[derive(Debug, Clone)]
52pub struct VerificationTest {
53    /// Description of the test case
54    pub description: String,
55    /// Input values
56    pub inputs: HashMap<String, f64>,
57    /// Expected output value
58    pub expected: f64,
59    /// Tolerance for comparison
60    pub tolerance: f64,
61    /// Source of the analytical solution
62    pub source: Option<String>,
63}
64
65impl VerificationTest {
66    /// Create a new verification test.
67    #[must_use]
68    pub fn new(description: &str, expected: f64, tolerance: f64) -> Self {
69        Self {
70            description: description.to_string(),
71            inputs: HashMap::new(),
72            expected,
73            tolerance,
74            source: None,
75        }
76    }
77
78    /// Add an input value.
79    #[must_use]
80    pub fn with_input(mut self, name: &str, value: f64) -> Self {
81        self.inputs.insert(name.to_string(), value);
82        self
83    }
84
85    /// Add the source reference.
86    #[must_use]
87    pub fn with_source(mut self, source: &str) -> Self {
88        self.source = Some(source.to_string());
89        self
90    }
91}
92
93/// Falsification criterion defining how to disprove the model.
94#[derive(Debug, Clone)]
95pub struct FalsificationCriterion {
96    /// Name of the criterion
97    pub name: String,
98    /// Description of what would disprove the model
99    pub description: String,
100    /// Statistical test to use
101    pub test_method: String,
102    /// Significance level (e.g., 0.05)
103    pub alpha: f64,
104}
105
106/// Implementation notes for numerical considerations.
107#[derive(Debug, Clone)]
108pub struct ImplementationNote {
109    /// Topic (e.g., "Numerical Stability")
110    pub topic: String,
111    /// The note content
112    pub content: String,
113    /// Severity/importance
114    pub importance: NoteImportance,
115}
116
117/// Importance level for implementation notes.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum NoteImportance {
120    /// Informational only
121    Info,
122    /// Should be considered
123    Important,
124    /// Must be addressed
125    Critical,
126}
127
128/// Equation Model Card - Complete documentation for a governing equation.
129///
130/// An EMC must be attached to any simulation using the equation. It provides:
131/// - Traceability to peer-reviewed literature
132/// - Verification test cases from analytical solutions
133/// - Domain constraints for valid operation
134/// - Falsification criteria for scientific validity
135#[derive(Debug, Clone)]
136pub struct EquationModelCard {
137    /// Unique name for this EMC
138    pub name: String,
139    /// Version string
140    pub version: String,
141    /// The governing equation in LaTeX
142    pub equation: String,
143    /// Equation classification
144    pub class: EquationClass,
145    /// Primary citation
146    pub citation: Citation,
147    /// Additional references
148    pub references: Vec<Citation>,
149    /// Variables in the equation
150    pub variables: Vec<EquationVariable>,
151    /// Verification tests
152    pub verification_tests: Vec<VerificationTest>,
153    /// Domain constraints
154    pub domain_constraints: Vec<DomainConstraint>,
155    /// Falsification criteria
156    pub falsification_criteria: Vec<FalsificationCriterion>,
157    /// Implementation notes
158    pub implementation_notes: Vec<ImplementationNote>,
159    /// Description/abstract
160    pub description: String,
161    /// Parent EMCs this derives from
162    pub lineage: Vec<String>,
163}
164
165/// Builder for `EquationModelCard`.
166///
167/// Enforces that all required fields are provided before building.
168#[derive(Debug, Default)]
169pub struct EmcBuilder {
170    name: Option<String>,
171    version: String,
172    equation: Option<String>,
173    class: Option<EquationClass>,
174    citation: Option<Citation>,
175    references: Vec<Citation>,
176    variables: Vec<EquationVariable>,
177    verification_tests: Vec<VerificationTest>,
178    domain_constraints: Vec<DomainConstraint>,
179    falsification_criteria: Vec<FalsificationCriterion>,
180    implementation_notes: Vec<ImplementationNote>,
181    description: String,
182    lineage: Vec<String>,
183}
184
185impl EmcBuilder {
186    /// Create a new EMC builder.
187    #[must_use]
188    pub fn new() -> Self {
189        Self {
190            version: "1.0.0".to_string(),
191            ..Default::default()
192        }
193    }
194
195    /// Set the EMC name.
196    #[must_use]
197    pub fn name(mut self, name: &str) -> Self {
198        self.name = Some(name.to_string());
199        self
200    }
201
202    /// Set the version.
203    #[must_use]
204    pub fn version(mut self, version: &str) -> Self {
205        self.version = version.to_string();
206        self
207    }
208
209    /// Set the governing equation (LaTeX).
210    #[must_use]
211    pub fn equation(mut self, equation: &str) -> Self {
212        self.equation = Some(equation.to_string());
213        self
214    }
215
216    /// Set the equation class.
217    #[must_use]
218    pub fn class(mut self, class: EquationClass) -> Self {
219        self.class = Some(class);
220        self
221    }
222
223    /// Set the primary citation.
224    #[must_use]
225    pub fn citation(mut self, citation: Citation) -> Self {
226        self.citation = Some(citation);
227        self
228    }
229
230    /// Add an additional reference.
231    #[must_use]
232    pub fn add_reference(mut self, reference: Citation) -> Self {
233        self.references.push(reference);
234        self
235    }
236
237    /// Add a variable.
238    #[must_use]
239    pub fn add_variable(mut self, symbol: &str, name: &str, units: &str) -> Self {
240        self.variables
241            .push(EquationVariable::new(symbol, name, units));
242        self
243    }
244
245    /// Add a variable with full specification.
246    #[must_use]
247    pub fn add_variable_full(mut self, variable: EquationVariable) -> Self {
248        self.variables.push(variable);
249        self
250    }
251
252    /// Add a verification test.
253    #[must_use]
254    pub fn add_verification_test(
255        mut self,
256        description: &str,
257        expected: f64,
258        tolerance: f64,
259    ) -> Self {
260        self.verification_tests
261            .push(VerificationTest::new(description, expected, tolerance));
262        self
263    }
264
265    /// Add a verification test with full specification.
266    #[must_use]
267    pub fn add_verification_test_full(mut self, test: VerificationTest) -> Self {
268        self.verification_tests.push(test);
269        self
270    }
271
272    /// Add a domain constraint.
273    #[must_use]
274    pub fn add_domain_constraint(mut self, constraint: DomainConstraint) -> Self {
275        self.domain_constraints.push(constraint);
276        self
277    }
278
279    /// Add a falsification criterion.
280    #[must_use]
281    pub fn add_falsification_criterion(mut self, criterion: FalsificationCriterion) -> Self {
282        self.falsification_criteria.push(criterion);
283        self
284    }
285
286    /// Add an implementation note.
287    #[must_use]
288    pub fn add_implementation_note(mut self, note: ImplementationNote) -> Self {
289        self.implementation_notes.push(note);
290        self
291    }
292
293    /// Set the description.
294    #[must_use]
295    pub fn description(mut self, description: &str) -> Self {
296        self.description = description.to_string();
297        self
298    }
299
300    /// Add a parent EMC to the lineage.
301    #[must_use]
302    pub fn add_lineage(mut self, parent: &str) -> Self {
303        self.lineage.push(parent.to_string());
304        self
305    }
306
307    /// Build the EMC, returning an error if required fields are missing.
308    ///
309    /// # Required Fields
310    /// - name
311    /// - equation
312    /// - citation
313    /// - At least one verification test
314    ///
315    /// # Errors
316    /// Returns `Err` with a description of missing fields.
317    pub fn build(self) -> Result<EquationModelCard, String> {
318        let name = self.name.ok_or("EMC requires a name")?;
319        let equation = self.equation.ok_or("EMC requires a governing equation")?;
320        let citation = self
321            .citation
322            .ok_or("EMC requires a citation (analytical derivation)")?;
323
324        if self.verification_tests.is_empty() {
325            return Err("EMC requires at least one verification test".to_string());
326        }
327
328        Ok(EquationModelCard {
329            name,
330            version: self.version,
331            equation,
332            class: self.class.unwrap_or(EquationClass::Conservation),
333            citation,
334            references: self.references,
335            variables: self.variables,
336            verification_tests: self.verification_tests,
337            domain_constraints: self.domain_constraints,
338            falsification_criteria: self.falsification_criteria,
339            implementation_notes: self.implementation_notes,
340            description: self.description,
341            lineage: self.lineage,
342        })
343    }
344}
345
346impl EquationModelCard {
347    /// Create a new EMC builder.
348    #[must_use]
349    pub fn builder() -> EmcBuilder {
350        EmcBuilder::new()
351    }
352
353    /// Create an EMC from a `GoverningEquation` implementation.
354    #[must_use]
355    pub fn from_equation<E: GoverningEquation>(equation: &E) -> EmcBuilder {
356        EmcBuilder::new()
357            .name(equation.name())
358            .equation(equation.latex())
359            .class(equation.class())
360            .citation(equation.citation())
361            .description(equation.description())
362    }
363
364    /// Run all verification tests.
365    ///
366    /// Returns a list of (`test_name`, passed, message) tuples.
367    pub fn run_verification_tests<F>(&self, evaluator: F) -> Vec<(String, bool, String)>
368    where
369        F: Fn(&HashMap<String, f64>) -> f64,
370    {
371        self.verification_tests
372            .iter()
373            .map(|test| {
374                let actual = evaluator(&test.inputs);
375                let passed = (actual - test.expected).abs() <= test.tolerance;
376                let message = if passed {
377                    format!(
378                        "PASS: {} (expected={}, actual={})",
379                        test.description, test.expected, actual
380                    )
381                } else {
382                    format!(
383                        "FAIL: {} (expected={}, actual={}, tolerance={})",
384                        test.description, test.expected, actual, test.tolerance
385                    )
386                };
387                (test.description.clone(), passed, message)
388            })
389            .collect()
390    }
391
392    /// Check if all verification tests pass.
393    ///
394    /// # Errors
395    /// Returns a list of failure messages if any verification tests fail.
396    pub fn verify<F>(&self, evaluator: F) -> Result<(), Vec<String>>
397    where
398        F: Fn(&HashMap<String, f64>) -> f64,
399    {
400        let results = self.run_verification_tests(evaluator);
401        let failures: Vec<String> = results
402            .into_iter()
403            .filter(|(_, passed, _)| !passed)
404            .map(|(_, _, msg)| msg)
405            .collect();
406
407        if failures.is_empty() {
408            Ok(())
409        } else {
410            Err(failures)
411        }
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn test_emc_builder_requires_name() {
421        let result = EmcBuilder::new()
422            .equation("L = \\lambda W")
423            .citation(Citation::new(&["Test"], "Test", 2024))
424            .add_verification_test("test", 1.0, 0.1)
425            .build();
426
427        assert!(result.is_err());
428        assert!(result.err().map(|e| e.contains("name")).unwrap_or(false));
429    }
430
431    #[test]
432    fn test_emc_builder_requires_equation() {
433        let result = EmcBuilder::new()
434            .name("Test EMC")
435            .citation(Citation::new(&["Test"], "Test", 2024))
436            .add_verification_test("test", 1.0, 0.1)
437            .build();
438
439        assert!(result.is_err());
440        assert!(result
441            .err()
442            .map(|e| e.contains("equation"))
443            .unwrap_or(false));
444    }
445
446    #[test]
447    fn test_emc_builder_requires_citation() {
448        let result = EmcBuilder::new()
449            .name("Test EMC")
450            .equation("L = \\lambda W")
451            .add_verification_test("test", 1.0, 0.1)
452            .build();
453
454        assert!(result.is_err());
455        assert!(result
456            .err()
457            .map(|e| e.contains("citation"))
458            .unwrap_or(false));
459    }
460
461    #[test]
462    fn test_emc_builder_requires_verification_tests() {
463        let result = EmcBuilder::new()
464            .name("Test EMC")
465            .equation("L = \\lambda W")
466            .citation(Citation::new(&["Test"], "Test", 2024))
467            .build();
468
469        assert!(result.is_err());
470        assert!(result
471            .err()
472            .map(|e| e.contains("verification"))
473            .unwrap_or(false));
474    }
475
476    #[test]
477    fn test_emc_builder_complete_builds() {
478        let result = EmcBuilder::new()
479            .name("Little's Law")
480            .equation("L = \\lambda W")
481            .citation(Citation::new(
482                &["Little, J.D.C."],
483                "Operations Research",
484                1961,
485            ))
486            .add_variable("L", "wip", "items")
487            .add_variable("lambda", "arrival_rate", "items/time")
488            .add_variable("W", "cycle_time", "time")
489            .add_verification_test("L = λW for λ=5, W=2 => L=10", 10.0, 1e-10)
490            .build();
491
492        assert!(result.is_ok());
493        let emc = result.ok();
494        assert!(emc.is_some());
495        let emc = emc.unwrap();
496        assert_eq!(emc.name, "Little's Law");
497        assert_eq!(emc.variables.len(), 3);
498        assert_eq!(emc.verification_tests.len(), 1);
499    }
500
501    #[test]
502    fn test_emc_run_verification_tests() {
503        let emc = EmcBuilder::new()
504            .name("Test")
505            .equation("y = x")
506            .citation(Citation::new(&["Test"], "Test", 2024))
507            .add_verification_test_full(
508                VerificationTest::new("identity", 5.0, 0.1).with_input("x", 5.0),
509            )
510            .build()
511            .ok();
512
513        assert!(emc.is_some());
514        let emc = emc.unwrap();
515        let results = emc.run_verification_tests(|inputs| inputs.get("x").copied().unwrap_or(0.0));
516
517        assert_eq!(results.len(), 1);
518        assert!(results[0].1); // passed
519    }
520
521    #[test]
522    fn test_domain_constraint() {
523        let constraint = DomainConstraint {
524            name: "Utilization bound".to_string(),
525            expression: "0 < ρ < 1".to_string(),
526            rationale: "Utilization must be less than 100% for stable queue".to_string(),
527            violation_behavior: ViolationBehavior::Halt,
528        };
529
530        assert_eq!(constraint.violation_behavior, ViolationBehavior::Halt);
531    }
532
533    #[test]
534    fn test_falsification_criterion() {
535        let criterion = FalsificationCriterion {
536            name: "Little's Law".to_string(),
537            description: "L should equal λW within statistical tolerance".to_string(),
538            test_method: "Two-sample t-test".to_string(),
539            alpha: 0.05,
540        };
541
542        assert!((criterion.alpha - 0.05).abs() < f64::EPSILON);
543    }
544
545    #[test]
546    fn test_emc_verify_pass() {
547        let emc = EmcBuilder::new()
548            .name("Test")
549            .equation("y = x")
550            .citation(Citation::new(&["Test"], "Test", 2024))
551            .add_verification_test_full(
552                VerificationTest::new("identity", 5.0, 0.1).with_input("x", 5.0),
553            )
554            .build()
555            .ok()
556            .unwrap();
557
558        let result = emc.verify(|inputs| inputs.get("x").copied().unwrap_or(0.0));
559        assert!(result.is_ok());
560    }
561
562    #[test]
563    fn test_emc_verify_fail() {
564        let emc = EmcBuilder::new()
565            .name("Test")
566            .equation("y = 2*x")
567            .citation(Citation::new(&["Test"], "Test", 2024))
568            .add_verification_test_full(
569                VerificationTest::new("double", 10.0, 0.01).with_input("x", 5.0),
570            )
571            .build()
572            .ok()
573            .unwrap();
574
575        // Return wrong value to trigger failure
576        let result = emc.verify(|_inputs| 100.0);
577        assert!(result.is_err());
578        let failures = result.err().unwrap();
579        assert_eq!(failures.len(), 1);
580        assert!(failures[0].contains("FAIL"));
581    }
582
583    #[test]
584    fn test_emc_run_verification_tests_fail() {
585        let emc = EmcBuilder::new()
586            .name("Test")
587            .equation("y = x")
588            .citation(Citation::new(&["Test"], "Test", 2024))
589            .add_verification_test_full(
590                VerificationTest::new("identity", 5.0, 0.001).with_input("x", 5.0),
591            )
592            .build()
593            .ok()
594            .unwrap();
595
596        let results = emc.run_verification_tests(|_| 999.0); // Return wrong value
597        assert_eq!(results.len(), 1);
598        assert!(!results[0].1); // failed
599        assert!(results[0].2.contains("FAIL"));
600    }
601
602    #[test]
603    fn test_emc_builder_version() {
604        let emc = EmcBuilder::new()
605            .name("Test")
606            .equation("y = x")
607            .version("2.0.0")
608            .citation(Citation::new(&["Test"], "Test", 2024))
609            .add_verification_test("test", 1.0, 0.1)
610            .build()
611            .ok()
612            .unwrap();
613
614        assert_eq!(emc.version, "2.0.0");
615    }
616
617    #[test]
618    fn test_emc_builder_class() {
619        let emc = EmcBuilder::new()
620            .name("Test")
621            .equation("y = x")
622            .class(EquationClass::Optimization)
623            .citation(Citation::new(&["Test"], "Test", 2024))
624            .add_verification_test("test", 1.0, 0.1)
625            .build()
626            .ok()
627            .unwrap();
628
629        assert_eq!(emc.class, EquationClass::Optimization);
630    }
631
632    #[test]
633    fn test_emc_builder_add_reference() {
634        let emc = EmcBuilder::new()
635            .name("Test")
636            .equation("y = x")
637            .citation(Citation::new(&["Test"], "Test", 2024))
638            .add_reference(Citation::new(&["Other"], "Other Journal", 2020))
639            .add_verification_test("test", 1.0, 0.1)
640            .build()
641            .ok()
642            .unwrap();
643
644        assert_eq!(emc.references.len(), 1);
645    }
646
647    #[test]
648    fn test_emc_builder_add_variable_full() {
649        let var =
650            EquationVariable::new("x", "input", "units").with_description("Test input variable");
651
652        let emc = EmcBuilder::new()
653            .name("Test")
654            .equation("y = x")
655            .citation(Citation::new(&["Test"], "Test", 2024))
656            .add_variable_full(var)
657            .add_verification_test("test", 1.0, 0.1)
658            .build()
659            .ok()
660            .unwrap();
661
662        assert_eq!(emc.variables.len(), 1);
663        assert_eq!(emc.variables[0].description, "Test input variable");
664    }
665
666    #[test]
667    fn test_emc_builder_add_domain_constraint() {
668        let constraint = DomainConstraint {
669            name: "positivity".to_string(),
670            expression: "x > 0".to_string(),
671            rationale: "Must be positive".to_string(),
672            violation_behavior: ViolationBehavior::Warn,
673        };
674        let emc = EmcBuilder::new()
675            .name("Test")
676            .equation("y = x")
677            .citation(Citation::new(&["Test"], "Test", 2024))
678            .add_domain_constraint(constraint)
679            .add_verification_test("test", 1.0, 0.1)
680            .build()
681            .ok()
682            .unwrap();
683
684        assert_eq!(emc.domain_constraints.len(), 1);
685        assert_eq!(
686            emc.domain_constraints[0].violation_behavior,
687            ViolationBehavior::Warn
688        );
689    }
690
691    #[test]
692    fn test_emc_builder_add_falsification_criterion() {
693        let criterion = FalsificationCriterion {
694            name: "test".to_string(),
695            description: "description".to_string(),
696            test_method: "method".to_string(),
697            alpha: 0.01,
698        };
699        let emc = EmcBuilder::new()
700            .name("Test")
701            .equation("y = x")
702            .citation(Citation::new(&["Test"], "Test", 2024))
703            .add_falsification_criterion(criterion)
704            .add_verification_test("test", 1.0, 0.1)
705            .build()
706            .ok()
707            .unwrap();
708
709        assert_eq!(emc.falsification_criteria.len(), 1);
710        assert!((emc.falsification_criteria[0].alpha - 0.01).abs() < f64::EPSILON);
711    }
712
713    #[test]
714    fn test_emc_builder_add_implementation_note() {
715        let note = ImplementationNote {
716            topic: "Test Topic".to_string(),
717            content: "Important Note".to_string(),
718            importance: NoteImportance::Info,
719        };
720        let emc = EmcBuilder::new()
721            .name("Test")
722            .equation("y = x")
723            .citation(Citation::new(&["Test"], "Test", 2024))
724            .add_implementation_note(note)
725            .add_verification_test("test", 1.0, 0.1)
726            .build()
727            .ok()
728            .unwrap();
729
730        assert_eq!(emc.implementation_notes.len(), 1);
731        assert_eq!(emc.implementation_notes[0].content, "Important Note");
732    }
733
734    #[test]
735    fn test_emc_builder_description() {
736        let emc = EmcBuilder::new()
737            .name("Test")
738            .equation("y = x")
739            .citation(Citation::new(&["Test"], "Test", 2024))
740            .description("A test description")
741            .add_verification_test("test", 1.0, 0.1)
742            .build()
743            .ok()
744            .unwrap();
745
746        assert_eq!(emc.description, "A test description");
747    }
748
749    #[test]
750    fn test_emc_builder_add_lineage() {
751        let emc = EmcBuilder::new()
752            .name("Test")
753            .equation("y = x")
754            .citation(Citation::new(&["Test"], "Test", 2024))
755            .add_lineage("parent-emc")
756            .add_verification_test("test", 1.0, 0.1)
757            .build()
758            .ok()
759            .unwrap();
760
761        assert_eq!(emc.lineage.len(), 1);
762        assert_eq!(emc.lineage[0], "parent-emc");
763    }
764
765    #[test]
766    fn test_verification_test_builder() {
767        let test = VerificationTest::new("test desc", 10.0, 0.01)
768            .with_input("x", 5.0)
769            .with_input("y", 2.0);
770
771        assert_eq!(test.description, "test desc");
772        assert!((test.expected - 10.0).abs() < f64::EPSILON);
773        assert!((test.tolerance - 0.01).abs() < f64::EPSILON);
774        assert_eq!(test.inputs.len(), 2);
775    }
776
777    #[test]
778    fn test_implementation_note() {
779        let note = ImplementationNote {
780            topic: "Numerical".to_string(),
781            content: "Use Welford's algorithm for numerical stability".to_string(),
782            importance: NoteImportance::Important,
783        };
784        assert!(note.content.contains("Welford"));
785        assert_eq!(note.importance, NoteImportance::Important);
786    }
787
788    #[test]
789    fn test_note_importance_variants() {
790        assert_ne!(NoteImportance::Info, NoteImportance::Important);
791        assert_ne!(NoteImportance::Important, NoteImportance::Critical);
792        assert_ne!(NoteImportance::Critical, NoteImportance::Info);
793    }
794
795    #[test]
796    fn test_violation_behavior_variants() {
797        assert_ne!(ViolationBehavior::Halt, ViolationBehavior::Warn);
798        assert_ne!(ViolationBehavior::Warn, ViolationBehavior::Clamp);
799        assert_ne!(ViolationBehavior::Clamp, ViolationBehavior::Halt);
800    }
801
802    #[test]
803    fn test_equation_model_card_builder() {
804        let builder = EquationModelCard::builder();
805        assert!(builder.build().is_err());
806    }
807}