energyml_section/
inputs.rs1use crate::model::{CrossWidths, Crosses, Inputs};
12
13pub const MAX_CROSSES: u64 = 10_000;
16
17#[derive(Debug, Clone, PartialEq, thiserror::Error)]
19pub enum InputsError {
20 #[error("the trace {0}")]
23 Trace(String),
24 #[error("{0}")]
26 Setting(String),
27}
28
29pub fn validate(inputs: &Inputs) -> Result<(), InputsError> {
31 let trace = &inputs.trace;
32 if trace.len() < 2 {
33 return Err(InputsError::Trace(format!(
34 "needs at least two points, got {}",
35 trace.len()
36 )));
37 }
38 if let Some(i) = trace.iter().position(|p| !p[0].is_finite() || !p[1].is_finite()) {
39 return Err(InputsError::Trace(format!(
40 "point {i} has a coordinate that is not a finite number: [{}, {}]",
41 trace[i][0], trace[i][1]
42 )));
43 }
44 if let Some(i) = (1..trace.len()).find(|&i| trace[i] == trace[i - 1]) {
45 return Err(InputsError::Trace(format!(
46 "points {} and {i} coincide ([{}, {}]): a segment of no length has no direction, \
47 so no side",
48 i - 1,
49 trace[i][0],
50 trace[i][1]
51 )));
52 }
53 width("the left width", inputs.left)?;
54 width("the right width", inputs.right)?;
55 if inputs.left == 0.0 && inputs.right == 0.0 {
56 return Err(InputsError::Setting(
57 "the long section's left and right widths are both 0: its corridor would keep nothing"
58 .to_string(),
59 ));
60 }
61 if let Some(f) = inputs.time_factor
62 && (!f.is_finite() || f <= 0.0)
63 {
64 return Err(InputsError::Setting(format!(
65 "the time factor must be a finite number above 0, got {f}"
66 )));
67 }
68 match &inputs.crosses {
69 Crosses::None => {}
70 Crosses::Every { spacing, common, own } => {
71 if !spacing.is_finite() || *spacing <= 0.0 {
72 return Err(InputsError::Setting(format!(
73 "the cross sections' spacing must be a finite number above 0, got {spacing}"
74 )));
75 }
76 widths("the common cross sections'", common)?;
77 for o in own {
78 widths(&format!("cross section {}'s", o.index), &o.widths)?;
79 }
80 distinct(own.iter().map(|o| o.index))?;
81 }
82 Crosses::Count { count, common, own } => {
83 if *count < 1 {
84 return Err(InputsError::Setting(format!(
85 "the number of cross sections must be at least 1, got {count}"
86 )));
87 }
88 if u64::from(*count) > MAX_CROSSES {
89 return Err(InputsError::Setting(format!(
90 "{count} cross sections is over the limit of {MAX_CROSSES}"
91 )));
92 }
93 widths("the common cross sections'", common)?;
94 for o in own {
95 widths(&format!("cross section {}'s", o.index), &o.widths)?;
96 }
97 distinct(own.iter().map(|o| o.index))?;
98 }
99 }
100 Ok(())
101}
102
103fn width(name: &str, value: f64) -> Result<(), InputsError> {
104 if value.is_finite() && value >= 0.0 {
105 Ok(())
106 } else {
107 Err(InputsError::Setting(format!(
108 "{name} must be a finite number, not negative, got {value}"
109 )))
110 }
111}
112
113fn widths(who: &str, w: &CrossWidths) -> Result<(), InputsError> {
114 width(&format!("{who} length before"), w.before)?;
115 width(&format!("{who} length after"), w.after)?;
116 width(&format!("{who} left width"), w.left)?;
117 width(&format!("{who} right width"), w.right)?;
118 if w.before + w.after <= 0.0 {
119 return Err(InputsError::Setting(format!(
120 "{who} lengths before and after are both 0: a cross section of no length has no \
121 direction"
122 )));
123 }
124 if w.left == 0.0 && w.right == 0.0 {
125 return Err(InputsError::Setting(format!(
126 "{who} left and right widths are both 0: the corridor would keep nothing"
127 )));
128 }
129 Ok(())
130}
131
132fn distinct(indices: impl Iterator<Item = u32>) -> Result<(), InputsError> {
133 let mut seen = std::collections::HashSet::new();
134 for i in indices {
135 if !seen.insert(i) {
136 return Err(InputsError::Setting(format!(
137 "cross section {i} is set apart twice"
138 )));
139 }
140 }
141 Ok(())
142}