scientific_method/
method.rs1use crate::{
4 Conclusion, Control, Criterion, CriterionError, CriterionResult, DependentVariable, Hypothesis,
5 IndependentVariable, Observations, Outcome, Prediction, Question,
6};
7
8#[derive(Debug, Clone, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct ScientificMethod {
12 pub question: Option<Question>,
13 pub hypothesis: Hypothesis,
14 pub predictions: Vec<Prediction>,
15 pub independent_variables: Vec<IndependentVariable>,
16 pub dependent_variables: Vec<DependentVariable>,
17 pub controls: Vec<Control>,
18 pub criteria: Vec<Criterion>,
19}
20
21impl ScientificMethod {
22 pub fn new(hypothesis: Hypothesis) -> Self {
23 Self {
24 question: None,
25 hypothesis,
26 predictions: Vec::new(),
27 independent_variables: Vec::new(),
28 dependent_variables: Vec::new(),
29 controls: Vec::new(),
30 criteria: Vec::new(),
31 }
32 }
33
34 pub fn question(mut self, question: Question) -> Self {
35 self.question = Some(question);
36 self
37 }
38
39 pub fn prediction(mut self, prediction: Prediction) -> Self {
40 self.predictions.push(prediction);
41 self
42 }
43
44 pub fn independent(mut self, variable: IndependentVariable) -> Self {
45 self.independent_variables.push(variable);
46 self
47 }
48
49 pub fn dependent(mut self, variable: DependentVariable) -> Self {
50 self.dependent_variables.push(variable);
51 self
52 }
53
54 pub fn control(mut self, control: Control) -> Self {
55 self.controls.push(control);
56 self
57 }
58
59 pub fn criterion(mut self, criterion: Criterion) -> Self {
60 self.criteria.push(criterion);
61 self
62 }
63
64 pub fn evaluate(
66 &self,
67 observations: &Observations,
68 ) -> Result<MethodEvaluation, CriterionError> {
69 if self.criteria.is_empty() {
70 return Ok(MethodEvaluation {
71 criteria: Vec::new(),
72 conclusion: Conclusion::new(Outcome::Inconclusive),
73 });
74 }
75
76 let criteria = self
77 .criteria
78 .iter()
79 .map(|criterion| criterion.evaluate(observations))
80 .collect::<Result<Vec<_>, _>>()?;
81
82 let outcome = if criteria.iter().all(|result| result.passed) {
83 Outcome::Supported
84 } else {
85 Outcome::NotSupported
86 };
87
88 Ok(MethodEvaluation {
89 criteria,
90 conclusion: Conclusion::new(outcome),
91 })
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub struct MethodEvaluation {
99 pub criteria: Vec<CriterionResult>,
100 pub conclusion: Conclusion,
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use crate::{Comparator, Measurement, Observation, Operand};
107
108 #[test]
109 fn no_criteria_is_inconclusive() {
110 let method = ScientificMethod::new(Hypothesis::new("something changes"));
111 assert_eq!(
112 method
113 .evaluate(&Observations::new())
114 .unwrap()
115 .conclusion
116 .outcome,
117 Outcome::Inconclusive
118 );
119 }
120
121 #[test]
122 fn all_declared_criteria_must_pass() {
123 let method = ScientificMethod::new(Hypothesis::new("candidate is valid"))
124 .criterion(Criterion::compare(
125 Operand::observation("candidate", "tests_pass"),
126 Comparator::Equal,
127 Operand::literal(true),
128 ))
129 .criterion(Criterion::compare(
130 Operand::observation("candidate", "checksum_matches"),
131 Comparator::Equal,
132 Operand::literal(true),
133 ));
134
135 let observations = Observations::new()
136 .with(Observation::new(
137 "candidate",
138 Measurement::new("tests_pass", true),
139 ))
140 .with(Observation::new(
141 "candidate",
142 Measurement::new("checksum_matches", false),
143 ));
144
145 assert_eq!(
146 method.evaluate(&observations).unwrap().conclusion.outcome,
147 Outcome::NotSupported
148 );
149 }
150}