1#![warn(
3 clippy::all,
4 clippy::restriction,
5 clippy::pedantic,
6 clippy::nursery,
7 clippy::cargo,
8 rust_2018_idioms,
9 missing_debug_implementations,
10 missing_docs
11)]
12#![allow(clippy::module_inception)]
13#![allow(clippy::implicit_return)]
14#![allow(clippy::blanket_clippy_restriction_lints)]
15#![allow(clippy::shadow_same)]
16#![allow(clippy::module_name_repetitions)]
17#![allow(clippy::cargo_common_metadata)]
18#![allow(clippy::separated_literal_suffix)]
19#![allow(clippy::float_arithmetic)]
20#![allow(clippy::struct_excessive_bools)]
21
22use anyhow::{anyhow, Error, Result};
23use clap::Parser;
24use dialoguer::Input;
25use dirs::home_dir;
26use log::info;
27use prettytable::{format, Cell, Row, Table};
28use serde_json::json;
29use sled::Db;
30use std::fmt::{Display, Formatter};
31use std::str::FromStr;
32
33pub mod prelude {
35 pub use crate::{
36 calculate_posterior_probability, get_prior, remove_prior, report_posterior_probability,
37 set_prior, wizard, Args, Evidence, UpdateHypothesis,
38 };
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum Evidence {
45 Observed,
47 NotObserved,
49}
50
51impl FromStr for Evidence {
52 type Err = Error;
53
54 #[inline]
55 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
56 match s {
57 "o" | "observed" | "Observed" | "y" | "Y" => Ok(Self::Observed),
58 "n" | "not-observed" | "NotObserved" | "N" | "not observed" => Ok(Self::NotObserved),
59 _ => Err(anyhow!("Invalid evidence: {}", s)),
60 }
61 }
62}
63
64impl Display for Evidence {
65 #[inline]
66 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67 match *self {
68 Self::Observed => write!(f, "Observed"),
69 Self::NotObserved => write!(f, "NotObserved"),
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76#[non_exhaustive]
77pub enum UpdateHypothesis {
78 Update,
80 NoUpdate,
82}
83
84impl FromStr for UpdateHypothesis {
85 type Err = Error;
86
87 #[inline]
88 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
89 match s {
90 "u" | "update" | "Update" | "y" | "Y" => Ok(Self::Update),
91 "n" | "no-update" | "NoUpdate" | "N" => Ok(Self::NoUpdate),
92 _ => Err(anyhow!("Invalid update hypothesis: {}", s)),
93 }
94 }
95}
96
97impl Display for UpdateHypothesis {
98 #[inline]
99 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100 match *self {
101 Self::Update => write!(f, "Update"),
102 Self::NoUpdate => write!(f, "NoUpdate"),
103 }
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109#[non_exhaustive]
110pub enum OutputFormat {
111 Table,
113 Json,
115 Simple,
117}
118
119impl FromStr for OutputFormat {
120 type Err = Error;
121
122 #[inline]
123 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
124 match s {
125 "table" | "Table" | "t" | "T" => Ok(Self::Table),
126 "json" | "Json" | "j" | "J" => Ok(Self::Json),
127 "simple" | "Simple" | "s" | "S" => Ok(Self::Simple),
128 _ => Err(anyhow!("Invalid output format: {}", s)),
129 }
130 }
131}
132
133impl Display for OutputFormat {
134 #[inline]
135 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136 match *self {
137 Self::Table => write!(f, "Table"),
138 Self::Json => write!(f, "Json"),
139 Self::Simple => write!(f, "Simple"),
140 }
141 }
142}
143
144#[derive(Parser, Debug)]
146#[non_exhaustive]
147#[clap(author, version, about, long_about = None)]
148pub struct Args {
149 #[clap(
151 short,
152 long,
153 forbid_empty_values = true,
154 required_unless_present("wizard")
155 )]
156 pub name: Option<String>,
157 #[clap(
159 short,
160 long,
161 default_value_if("name", None, Some("0.5")),
162 validator = parse_validate_probability,
163 forbid_empty_values = true,
164 required_unless_present("wizard")
165 )]
166 pub prior: Option<f64>,
167 #[clap(
169 short,
170 long,
171 default_value_if("name", None, Some("0.5")),
172 validator = parse_validate_probability,
173 forbid_empty_values = true,
174 required_unless_present("wizard"))]
175 pub likelihood: Option<f64>,
176 #[clap(
178 long,
179 default_value_if("name", None, Some("0.5")),
180 validator = parse_validate_probability,
181 forbid_empty_values = true,
182 required_unless_present("wizard"))]
183 pub likelihood_null: Option<f64>,
184 #[clap(
186 short,
187 long,
188 default_value_if("name", None, Some("Observed")),
189 default_missing_value = "Observed",
190 possible_values = ["o", "observed", "Observed", "n", "not-observed", "NotObserved"],
191 required_unless_present("wizard"))]
192 pub evidence: Option<Evidence>,
193 #[clap(
195 short,
196 long,
197 default_value_if("name", None, Some("NoUpdate")),
198 default_missing_value = "Update",
199 possible_values = ["u", "update", "Update", "n", "no-update", "NoUpdate"])]
200 pub update_prior: Option<UpdateHypothesis>,
201 #[clap(
204 short,
205 long,
206 conflicts_with = "prior",
207 conflicts_with = "likelihood",
208 conflicts_with = "likelihood-null",
209 conflicts_with = "evidence",
210 conflicts_with = "update-prior"
211 )]
212 pub get_prior: bool,
213 #[clap(
216 short,
217 long,
218 validator = parse_validate_probability,
219 conflicts_with = "prior",
220 conflicts_with = "likelihood",
221 conflicts_with = "likelihood-null",
222 conflicts_with = "evidence",
223 conflicts_with = "update-prior",
224 conflicts_with = "get-prior"
225 )]
226 pub set_prior: Option<f64>,
227 #[clap(
230 short,
231 long,
232 conflicts_with = "prior",
233 conflicts_with = "likelihood",
234 conflicts_with = "likelihood-null",
235 conflicts_with = "evidence",
236 conflicts_with = "update-prior",
237 conflicts_with = "set-prior",
238 conflicts_with = "get-prior"
239 )]
240 pub remove_prior: bool,
241 #[clap(short, long, exclusive = true, takes_value = false)]
243 pub wizard: bool,
244 #[clap(
246 short,
247 long,
248 default_value_if("name", None, Some("Table")),
249 possible_values = ["t", "table", "Table", "T", "j", "json", "Json", "J", "s", "simple", "Simple", "S"],
250 required_unless_present("wizard")
251 )]
252 pub output: Option<OutputFormat>,
253}
254
255#[inline]
259pub fn calculate_posterior_probability(
260 prior: f64,
261 likelihood: f64,
262 likelihood_null: f64,
263 evidence: &Evidence,
264 name: &str,
265) -> Result<f64> {
266 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, evidence, name)?;
267 let p_e = marginal_likelihood(prior, likelihood, likelihood_null);
268 match *evidence {
269 Evidence::Observed => {
270 Ok(likelihood * prior / p_e)
272 }
273 Evidence::NotObserved => {
274 Ok(negate(likelihood) * prior / negate(p_e))
276 }
277 }
278}
279
280#[inline]
286#[cfg(not(tarpaulin_include))]
287pub fn get_prior(name: &str) -> Result<f64> {
288 let db = open_db()?;
289 let prior = db.get(&name)?;
290 match prior {
291 Some(prior_serialized) => {
292 let bytes = prior_serialized.as_ref();
293 Ok(f64::from_be_bytes(bytes.try_into()?))
294 }
295 None => return Err(anyhow!("Could not find hypothesis {name}")),
296 }
297}
298
299#[inline]
304#[cfg(not(tarpaulin_include))]
305pub fn set_prior(name: &str, prior: f64) -> Result<()> {
306 let db = open_db()?;
307 db.insert(name, &prior.to_be_bytes())?;
308 Ok(())
309}
310
311#[inline]
316#[cfg(not(tarpaulin_include))]
317pub fn remove_prior(name: &str) -> Result<()> {
318 let db = open_db()?;
319 db.remove(name)?;
320 Ok(())
321}
322
323#[inline]
327#[cfg(not(tarpaulin_include))]
328fn open_db() -> Result<Db> {
329 let hd = match home_dir() {
330 Some(hd) => hd,
331 None => return Err(anyhow!("Could not find home directory")),
332 };
333 let db_path = hd.join(".ask-bayes").join("hypotheses.db");
334 Ok(sled::open(db_path)?)
335}
336
337fn parse_validate_probability(value: &str) -> Result<f64> {
339 let float = value.parse::<f64>()?;
340 validate_probability(float)?;
341 Ok(float)
342}
343
344fn validate_probability(value: f64) -> Result<()> {
346 if !(0.0_f64..=1.0_f64).contains(&value) {
347 return Err(anyhow!("Probability must be between 0 and 1"));
348 }
349 Ok(())
350}
351
352fn negate(value: f64) -> f64 {
354 1.0_f64 - value
355}
356
357fn validate_likelihoods_and_prior(
359 prior: f64,
360 likelihood: f64,
361 likelihood_null: f64,
362 evidence: &Evidence,
363 name: &str,
364) -> Result<()> {
365 match *evidence {
366 Evidence::Observed => {
367 if marginal_likelihood(prior, likelihood, likelihood_null) <= 0.0_f64 {
368 return Err(anyhow!("The total probability of observing evidence P(E) must be greater than 0 if evidence is observed. \r\nP(E) = P({name})[{prior}] * P(E|{name})[{likelihood}] + P(\u{ac}{name})[{}] * P(E|\u{ac}{name})[{}] = 0", negate(prior), likelihood_null));
369 }
370 }
371 Evidence::NotObserved => {
372 if negate(marginal_likelihood(prior, likelihood, likelihood_null)) <= 0.0_f64 {
373 return Err(anyhow!("The total probability of not observing evidence P(\u{ac}E) must be greater than 0 if evidence is not observed. \r\nP(\u{ac}E) = P(\u{ac}E|{name})[{}] * P({name})[{prior}] + P(\u{ac}{name})[{}] * P(\u{ac}E|\u{ac}{name})[{}] = 0", negate(likelihood), negate(prior), negate(likelihood_null)));
374 }
375 }
376 }
377
378 Ok(())
379}
380
381fn marginal_likelihood(prior: f64, likelihood: f64, likelihood_null: f64) -> f64 {
383 likelihood.mul_add(prior, likelihood_null * negate(prior))
384}
385
386#[inline]
390#[cfg(not(tarpaulin_include))]
391pub fn wizard() -> Result<()> {
392 let name = Input::<String>::new()
393 .with_prompt("Enter the name of the hypothesis")
394 .allow_empty(false)
395 .interact_text()?;
396
397 let prior = Input::<f64>::new()
398 .with_prompt(format!(
399 "Enter the prior probability of the hypothesis P({name})"
400 ))
401 .allow_empty(false)
402 .default(0.5_f64)
403 .validate_with(|v: &f64| validate_probability(*v))
404 .interact_text()?;
405
406 let likelihood = Input::<f64>::new()
407 .with_prompt(format!(
408 "Enter the likelihood of observing evidence given {name} is true P(E|{name})"
409 ))
410 .allow_empty(false)
411 .default(0.5_f64)
412 .validate_with(|v: &f64| validate_probability(*v))
413 .interact_text()?;
414
415 let likelihood_null = Input::<f64>::new()
416 .with_prompt(format!(
417 "Enter the likelihood of observing evidence given {name} is false P(E|\u{ac}{name})"
418 ))
419 .allow_empty(false)
420 .default(0.5_f64)
421 .validate_with(|v: &f64| validate_probability(*v))
422 .interact_text()?;
423
424 let evidence = Input::<Evidence>::new()
425 .with_prompt("Is evidence observed or not observed?".to_owned())
426 .allow_empty(false)
427 .default(Evidence::Observed)
428 .interact_text()?;
429
430 let posterior_probability =
431 calculate_posterior_probability(prior, likelihood, likelihood_null, &evidence, &name)?;
432
433 let output_format = Input::<OutputFormat>::new()
434 .with_prompt("How would you like the output?".to_owned())
435 .allow_empty(false)
436 .default(OutputFormat::Table)
437 .interact_text()?;
438
439 report_posterior_probability(
440 prior,
441 likelihood,
442 likelihood_null,
443 &evidence,
444 posterior_probability,
445 &name,
446 &output_format,
447 );
448
449 let update = Input::<UpdateHypothesis>::new()
450 .with_prompt("Would you like to update the prior probability?".to_owned())
451 .allow_empty(false)
452 .default(UpdateHypothesis::NoUpdate)
453 .interact_text()?;
454
455 if update == UpdateHypothesis::Update {
456 set_prior(&name, posterior_probability)?;
457 info!("P({name}) has been updated to {}", posterior_probability);
458 }
459
460 Ok(())
461}
462
463#[inline]
465#[cfg(not(tarpaulin_include))]
466pub fn report_posterior_probability(
467 prior: f64,
468 likelihood: f64,
469 likelihood_null: f64,
470 evidence: &Evidence,
471 posterior_probability: f64,
472 name: &str,
473 output_format: &OutputFormat,
474) {
475 match *output_format {
476 OutputFormat::Table => {
477 report_table(
478 name,
479 prior,
480 likelihood,
481 likelihood_null,
482 evidence,
483 posterior_probability,
484 );
485 }
486 OutputFormat::Json => {
487 report_json(
488 name,
489 prior,
490 likelihood,
491 likelihood_null,
492 evidence,
493 posterior_probability,
494 );
495 }
496 OutputFormat::Simple => {
497 let output = format!(
498 "
499 P({name}) = {prior}
500 P(E|{name}) = {likelihood}
501 P(E|\u{ac}{name}) = {likelihood_null}
502 P({name}|E) = {posterior_probability}
503 "
504 );
505 info!("{output}");
506 }
507 }
508}
509
510#[cfg(not(tarpaulin_include))]
512fn report_table(
513 name: &str,
514 prior: f64,
515 likelihood: f64,
516 likelihood_null: f64,
517 evidence: &Evidence,
518 posterior_probability: f64,
519) {
520 let marginal_likelihood = marginal_likelihood(prior, likelihood, likelihood_null);
521 let mut table = Table::new();
522 table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
523 table.set_titles(Row::new(vec![
524 Cell::new("Name"),
525 Cell::new("Probability"),
526 Cell::new("Value"),
527 ]));
528 table.add_row(Row::new(vec![
529 Cell::new("Prior"),
530 Cell::new(&format!("P({name})")),
531 Cell::new(&format!("{prior}")),
532 ]));
533 table.add_row(Row::new(vec![
534 Cell::new("Likelihood"),
535 Cell::new(&format!("P(E|{name})")),
536 Cell::new(&format!("{likelihood}")),
537 ]));
538 table.add_row(Row::new(vec![
539 Cell::new("Likelihood Null"),
540 Cell::new(&format!("P(E|\u{ac}{name})")),
541 Cell::new(&format!("{likelihood_null}")),
542 ]));
543 table.add_row(Row::new(vec![
544 Cell::new("Marginal Likelihood"),
545 Cell::new("P(E)"),
546 Cell::new(&format!("{marginal_likelihood}")),
547 ]));
548
549 match *evidence {
550 Evidence::Observed => table.add_row(Row::new(vec![
551 Cell::new("Posterior Probability"),
552 Cell::new(&format!("P({name}|E)")),
553 Cell::new(&format!("{posterior_probability}")),
554 ])),
555 Evidence::NotObserved => table.add_row(Row::new(vec![
556 Cell::new(&format!("P({name}|\u{ac}E)")),
557 Cell::new(&format!("{posterior_probability}")),
558 ])),
559 };
560
561 table.printstd();
562}
563
564#[cfg(not(tarpaulin_include))]
566fn report_json(
567 name: &str,
568 prior: f64,
569 likelihood: f64,
570 likelihood_null: f64,
571 evidence: &Evidence,
572 posterior_probability: f64,
573) {
574 let json = json!({
575 "name": name,
576 "prior": prior,
577 "likelihood": likelihood,
578 "likelihood_null": likelihood_null,
579 "evidence": match *evidence {
580 Evidence::Observed => "observed",
581 Evidence::NotObserved => "not observed",
582 },
583 "posterior_probability": posterior_probability,
584 });
585
586 info!("{}", json.to_string());
587}
588
589#[cfg(test)]
590#[allow(clippy::panic_in_result_fn)]
591mod tests {
592 use super::*;
593
594 fn epsilon_compare(a: f64, b: f64) -> bool {
595 (a - b).abs() < f64::EPSILON
596 }
597
598 #[test]
599 fn it_validates_a_valid_probability() -> Result<()> {
600 let prob = "0.75";
601 let result = parse_validate_probability(prob)?;
602 assert!(epsilon_compare(result, 0.75_f64));
603 Ok(())
604 }
605
606 #[test]
607 fn it_fails_to_validate_a_probability_greater_than_1() {
608 let prob = "1.1";
609 let result = parse_validate_probability(prob);
610 assert!(result.is_err());
611 }
612
613 #[test]
614 fn it_fails_to_validate_a_probability_less_than_0() {
615 let prob = "-0.1";
616 let result = parse_validate_probability(prob);
617 assert!(result.is_err());
618 }
619
620 #[test]
621 fn it_fails_to_validate_an_invalid_float() {
622 let prob = "invalid";
623 let result = parse_validate_probability(prob);
624 assert!(result.is_err());
625 }
626
627 #[test]
628 fn it_validates_a_valid_pair_of_likelihoods() -> Result<()> {
629 let likelihood = 0.75_f64;
630 let likelihood_null = 0.25_f64;
631 let prior = 0.5_f64;
632 let evidence = Evidence::Observed;
633 let name = "test";
634 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name)
635 }
636
637 #[test]
638 fn it_validates_a_valid_pair_of_negated_likelihoods() -> Result<()> {
639 let prior = 0.5_f64;
640 let likelihood = 0.75_f64;
641 let likelihood_null = 0.25_f64;
642 let evidence = Evidence::NotObserved;
643 let name = "test";
644 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name)
645 }
646
647 #[test]
648 fn it_fails_to_validate_a_pair_of_likelihoods_with_evidence_observed_when_the_sum_is_less_than_or_equal_to_0(
649 ) {
650 let prior = 0.5_f64;
651 let likelihood = 0.0_f64;
652 let likelihood_null = 0.0_f64;
653 let evidence = Evidence::Observed;
654 let name = "test";
655 let result =
656 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name);
657 assert!(result.is_err());
658 }
659
660 #[test]
661 fn it_fails_to_validate_a_pair_of_negated_likelihoods_with_evidence_not_observed_when_the_negated_sum_is_less_than_or_equal_to_0(
662 ) {
663 let prior = 0.5_f64;
664 let likelihood = 1.0_f64;
665 let likelihood_null = 1.0_f64;
666 let evidence = Evidence::NotObserved;
667 let name = "test";
668 let result =
669 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name);
670 assert!(result.is_err());
671 }
672
673 #[test]
674 fn it_parses_a_valid_evidence_string() -> Result<()> {
675 {
676 let evidence = "observed";
677 let result = Evidence::from_str(evidence)?;
678 assert_eq!(result, Evidence::Observed);
679 }
680 {
681 let evidence = "o";
682 let result = Evidence::from_str(evidence)?;
683 assert_eq!(result, Evidence::Observed);
684 }
685 {
686 let evidence = "Observed";
687 let result = Evidence::from_str(evidence)?;
688 assert_eq!(result, Evidence::Observed);
689 }
690 {
691 let evidence = "not-observed";
692 let result = Evidence::from_str(evidence)?;
693 assert_eq!(result, Evidence::NotObserved);
694 }
695 {
696 let evidence = "n";
697 let result = Evidence::from_str(evidence)?;
698 assert_eq!(result, Evidence::NotObserved);
699 }
700 {
701 let evidence = "NotObserved";
702 let result = Evidence::from_str(evidence)?;
703 assert_eq!(result, Evidence::NotObserved);
704 }
705 Ok(())
706 }
707
708 #[test]
709 fn it_fails_to_parse_an_invalid_evidence_string() {
710 let evidence = "invalid";
711 let result = Evidence::from_str(evidence);
712 assert!(result.is_err());
713 }
714
715 #[test]
716 fn it_displays_a_valid_evidence_string() {
717 {
718 let evidence = Evidence::Observed;
719 let result = evidence.to_string();
720 assert_eq!(result, "Observed");
721 }
722 {
723 let evidence = Evidence::NotObserved;
724 let result = evidence.to_string();
725 assert_eq!(result, "NotObserved");
726 }
727 }
728
729 #[test]
730 fn it_parses_a_valid_update_string() -> Result<()> {
731 {
732 let update = "update";
733 let result = UpdateHypothesis::from_str(update)?;
734 assert_eq!(result, UpdateHypothesis::Update);
735 }
736 {
737 let update = "u";
738 let result = UpdateHypothesis::from_str(update)?;
739 assert_eq!(result, UpdateHypothesis::Update);
740 }
741 {
742 let update = "Update";
743 let result = UpdateHypothesis::from_str(update)?;
744 assert_eq!(result, UpdateHypothesis::Update);
745 }
746 {
747 let update = "no-update";
748 let result = UpdateHypothesis::from_str(update)?;
749 assert_eq!(result, UpdateHypothesis::NoUpdate);
750 }
751 {
752 let update = "n";
753 let result = UpdateHypothesis::from_str(update)?;
754 assert_eq!(result, UpdateHypothesis::NoUpdate);
755 }
756 {
757 let update = "NoUpdate";
758 let result = UpdateHypothesis::from_str(update)?;
759 assert_eq!(result, UpdateHypothesis::NoUpdate);
760 }
761 Ok(())
762 }
763
764 #[test]
765 fn it_fails_to_parse_an_invalid_update_string() {
766 let update = "invalid";
767 let result = UpdateHypothesis::from_str(update);
768 assert!(result.is_err());
769 }
770
771 #[test]
772 fn it_displays_a_valid_update_string() {
773 {
774 let update = UpdateHypothesis::Update;
775 let result = update.to_string();
776 assert_eq!(result, "Update");
777 }
778 {
779 let update = UpdateHypothesis::NoUpdate;
780 let result = update.to_string();
781 assert_eq!(result, "NoUpdate");
782 }
783 }
784
785 #[test]
786 fn it_calculates_the_posterior_probability_when_evidence_is_observed() -> Result<()> {
787 let prior = 0.75_f64;
788 let likelihood = 0.75_f64;
789 let likelihood_null = 0.5_f64;
790 let evidence = Evidence::Observed;
791 let name = "test";
792 let result =
793 calculate_posterior_probability(prior, likelihood, likelihood_null, &evidence, name)?;
794 assert!(epsilon_compare(result, 0.818_181_818_181_818_2_f64));
795 Ok(())
796 }
797
798 #[test]
799 fn it_calculates_the_posterior_probability_when_evidence_is_not_observed() -> Result<()> {
800 let prior = 0.75_f64;
801 let likelihood = 0.75_f64;
802 let likelihood_null = 0.5_f64;
803 let evidence = Evidence::NotObserved;
804 let name = "test";
805 let result =
806 calculate_posterior_probability(prior, likelihood, likelihood_null, &evidence, name)?;
807 assert!(epsilon_compare(result, 0.6));
808 Ok(())
809 }
810
811 #[test]
812 fn it_fails_to_validate_likelihoods_and_hypothesis_when_the_negated_prior_is_zero() {
813 let name = "test";
814 let prior = 1.0_f64;
815 let likelihood = 1.0_f64;
816 let likelihood_null = 0.5_f64;
817 let evidence = Evidence::NotObserved;
818 let result =
819 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name);
820 assert!(result.is_err());
821 }
822
823 #[test]
824 fn it_fails_to_validate_likelihoods_and_hypothesis_when_the_prior_is_zero() {
825 let name = "test";
826 let prior = 0.0_f64;
827 let likelihood = 0.5_f64;
828 let likelihood_null = 0.0_f64;
829 let evidence = Evidence::Observed;
830 let result =
831 validate_likelihoods_and_prior(prior, likelihood, likelihood_null, &evidence, name);
832 assert!(result.is_err());
833 }
834
835 #[test]
836 fn it_parses_a_valid_output_format() -> Result<()> {
837 {
838 let format = "json";
839 let result = OutputFormat::from_str(format)?;
840 assert_eq!(result, OutputFormat::Json);
841 }
842 {
843 let format = "j";
844 let result = OutputFormat::from_str(format)?;
845 assert_eq!(result, OutputFormat::Json);
846 }
847 {
848 let format = "Json";
849 let result = OutputFormat::from_str(format)?;
850 assert_eq!(result, OutputFormat::Json);
851 }
852 {
853 let format = "J";
854 let result = OutputFormat::from_str(format)?;
855 assert_eq!(result, OutputFormat::Json);
856 }
857 {
858 let format = "t";
859 let result = OutputFormat::from_str(format)?;
860 assert_eq!(result, OutputFormat::Table);
861 }
862 {
863 let format = "Table";
864 let result = OutputFormat::from_str(format)?;
865 assert_eq!(result, OutputFormat::Table);
866 }
867 {
868 let format = "table";
869 let result = OutputFormat::from_str(format)?;
870 assert_eq!(result, OutputFormat::Table);
871 }
872 {
873 let format = "T";
874 let result = OutputFormat::from_str(format)?;
875 assert_eq!(result, OutputFormat::Table);
876 }
877 {
878 let format = "simple";
879 let result = OutputFormat::from_str(format)?;
880 assert_eq!(result, OutputFormat::Simple);
881 }
882 {
883 let format = "s";
884 let result = OutputFormat::from_str(format)?;
885 assert_eq!(result, OutputFormat::Simple);
886 }
887 {
888 let format = "S";
889 let result = OutputFormat::from_str(format)?;
890 assert_eq!(result, OutputFormat::Simple);
891 }
892 {
893 let format = "simple";
894 let result = OutputFormat::from_str(format)?;
895 assert_eq!(result, OutputFormat::Simple);
896 }
897 {
898 let format = "Simple";
899 let result = OutputFormat::from_str(format)?;
900 assert_eq!(result, OutputFormat::Simple);
901 }
902
903 Ok(())
904 }
905
906 #[test]
907 fn it_fails_to_parse_an_invalid_output_format() {
908 let format = "invalid";
909 let result = OutputFormat::from_str(format);
910 assert!(result.is_err());
911 }
912
913 #[test]
914 fn it_displays_a_valid_output_format() {
915 {
916 let format = OutputFormat::Json;
917 let result = format.to_string();
918 assert_eq!(result, "Json");
919 }
920 {
921 let format = OutputFormat::Table;
922 let result = format.to_string();
923 assert_eq!(result, "Table");
924 }
925 {
926 let format = OutputFormat::Simple;
927 let result = format.to_string();
928 assert_eq!(result, "Simple");
929 }
930 }
931}